# Troubleshooting

> What each failure actually means, why it happens, and the fix - grouped by where things go wrong.

Source: https://dispatchseo.com/docs/troubleshooting
All docs in one file: https://dispatchseo.com/llms-full.txt

Every entry below follows the same shape: what you see, what it means, and
what to do about it. If your problem isn't here, ask in the
[Discord](/discord) - that's where you'll get an answer fastest. For anything
worth keeping around for the next person,
[Discussions](https://github.com/NeoZi12/dispatchseo/discussions) works too.

<Callout type="note" title="Windows: use Git Bash">
The commands on this page are bash. On Windows run them in **Git Bash** (it
ships with [Git](https://git-scm.com/downloads/win)), not PowerShell. Anywhere
one ends in `sh start.sh`, running `.\start.cmd` from PowerShell afterwards
works too.
</Callout>

## Where failures surface

Every scheduled job logs its runs. A failure that persists - the same job
failing twice in a row, or missing a whole schedule window - shows as a red
banner on the dashboard Home page, and - if you set up the
[failure email](#get-emailed-when-something-breaks) - lands in your inbox
too, at most one email per job per day. Urgent problems (a broken deploy,
dead credentials, an empty balance) skip the wait and surface on their
first failure. A single one-off failed run does neither: it sits in the
run log and `get_cron_health`, and the next scheduled run usually cures
it on its own.

**A skipped job is not a failed job.** Capabilities you haven't finished
setting up - no Search Console yet, no keyword data source - skip quietly by
design, and the Home page's setup cards tell you what's missing. This is
deliberate: half-configured is a normal state during onboarding, so it never
raises an alarm.

**A delay is not a failure either.** A machine that was asleep or powered off
never emails you. Schedules catch up on their own, and only a job that fails
while the stack is actually running counts as broken.

## Start here

Three checks answer most problems in under a minute:

<Steps>
  <Step title="Is everything running?">
    In the `dispatchseo` folder: `docker compose ps`. Everything except the
    one-shot `migrate` container should say `running`, and `app` should reach
    `healthy` once it can talk to the database.
  </Step>
  <Step title="What does the dashboard say?">
    The Home page names the single next action and shows a red banner for any
    failed job. If it's asking for a setup step, that's your answer - nothing
    is broken.
  </Step>
  <Step title="What do the logs say?">
    `docker compose logs app`, or `logs cron`, or `logs builder`. The builder
    in particular states plainly what it's waiting for.
  </Step>
</Steps>

## Connecting your agent

<Symptom error="No such tool / server dispatchseo-... is not connected">
**What it means.** Claude Code loads MCP connections when a session starts. A
session that was already open when you added the connection cannot see it.

**Fixes, in order of likelihood:**

1. **Restart the session.** Close every open Claude Code session in that repo
   and start a fresh one. Confirm with `claude mcp list` in the repo folder -
   your `dispatchseo-<site>` entry should show a check mark.
2. **You're in the VS Code extension.** Its session runtime can miss a
   just-added connection even after restarting the chat. Run the paste from a
   plain terminal instead: open the repo folder and type `claude`. Or fully
   reload the VS Code window.
3. **Wrong folder.** The connection is pinned to the folder you ran the
   connect command in. Both pastes have to happen inside your site's repo.
4. **The key changed.** Keys rotate when a project is recreated. Copy the
   current connect command from **Settings → Project key** and run it again.
</Symptom>

<Symptom error="401 Unauthorized from /api/mcp">
**What it means.** The bearer token isn't valid for any project on this
install.

**Fix.** Get the current one from **Settings → Project key** and re-run the
connect command. If you self-host and recently restored a database or started
over with `docker compose down -v`, every previously issued key is gone -
reconnect each site.
</Symptom>

## Installing and starting

<Symptom error="Port 4005 is already in use">
**What it means.** Something else on the machine holds that port - often a
second DispatchSEO stack from an earlier attempt.

**Fix.** Pick any free port:

```bash
echo "DISPATCH_PORT=4006" >> .env && sh start.sh
```
</Symptom>

<Symptom error="Killed  /  exit code 137 during the build">
**What it means.** The machine ran out of memory compiling from source. This
happens on 1 GB servers when the prebuilt images can't be pulled.

**Fix.** Give the machine swap - spillover memory on disk - then start again:

```bash
fallocate -l 2G /swapfile && chmod 600 /swapfile && mkswap /swapfile &&
  swapon /swapfile && echo '/swapfile none swap sw 0 0' >> /etc/fstab
sh start.sh
```

The build gets slower but finishes, and the swap stays useful afterwards.
</Symptom>

<Symptom error="A container keeps restarting, or app never turns healthy">
**What it means.** Usually the app can't reach Postgres, or migrations
haven't finished.

**Fix.** `docker compose logs app` and `docker compose logs migrate` have the
real error. If the database is corrupt or you're mid-experiment, starting
clean is one command - **it deletes all data**:

```bash
docker compose down -v && sh start.sh
```
</Symptom>

<Symptom error="Pipeline update available (blue notice on Home)">
**What it means.** The SEO workflows in your site repo are a version behind
this backend. Not a failure - `sh start.sh` upgrades the stack but never
touches your site repo, so the workflow files there stay as they are until
you re-apply the pipeline pack. Everything keeps publishing meanwhile.

**Fix.** In a terminal, inside a checkout of your **site repo** (not the
`dispatchseo` folder):

```bash
claude "Call the dispatchseo-yoursite MCP tool get_instructions with workflow install and follow it exactly."
```

Swap `claude` for `codex` or `cursor-agent` if that's your agent, and
`dispatchseo-yoursite` for your project's MCP server name. Merge the pull
request it opens. Full walkthrough:
[Updating the pipeline in your site repo](/docs/upgrading#updating-the-pipeline-in-your-site-repo).
</Symptom>

## Nothing is being built

<Symptom error="The builder never opens a pull request">
**What it means.** Almost always one of the following, and the logs say which:
`docker compose logs builder`.

**Fixes:**

1. **No agent credential.** The builder needs your project's agent credential -
   `CLAUDE_CODE_OAUTH_TOKEN` (from `claude setup-token`) for Claude Code
   projects, `OPENAI_API_KEY` (from platform.openai.com/api-keys) for Codex
   projects, `CURSOR_API_KEY` (from cursor.com/dashboard/api) for Cursor
   projects. Easiest is pasting it in the credential box on Settings, which
   verifies it before storing; `.env` works too. You don't have to spot this
   in the logs any more: a job that comes due with no credential for its
   agent now writes a failed run - red banner on Home, and an email if you've
   set alerts up - naming what's missing, instead of being skipped in silence.
2. **The credential got line-wrapped on paste.** This one is nasty because it
   looks fine. Terminals wrap long values, and copying can bring a real newline
   with it, which silently invalidates them. Re-copy it in one piece and check
   there's no line break in `.env`.
3. **Setup isn't finished.** A project whose wizard never completed is skipped
   on purpose. The Home page's setup cards say what's missing.
4. **Codex only - the account can't run builds.** A real key on an account
   with no credit fails every build; the dashboard banner names it when this
   happens. Add credit at platform.openai.com/settings/organization/billing.
   A model 404 is the same family: builds default to `gpt-5`, and some
   accounts can't serve it - set `CODEX_MODEL=` in `.env` (or the
   `SEO_CODEX_MODEL` repo variable on GitHub-Actions installs).
5. **Cursor only - the plan can't run the pinned model, or is out of usage.**
   Free Cursor plans run only the `auto` model, so pinning one via
   `CURSOR_MODEL` in `.env` (or the `SEO_CURSOR_MODEL` repo variable) fails
   every build with "Named models unavailable" - unset it or upgrade the
   plan. A drained usage pool is gentler: builds report a deferral and retry
   by themselves once the plan's usage resets.
6. **Cloud only - the repo won't let Actions open pull requests.** The
   DispatchSEO GitHub App deliberately can't change your repo's own settings,
   so this one is yours to flip: repo **Settings → Actions → General → "Allow
   GitHub Actions to create and approve pull requests."** The onboarding
   finale names it with a direct link when it applies, and re-checks by
   itself once you turn it on.
</Symptom>

<Symptom error="Your organization has disabled Claude subscription access for Claude Code">
**What it means.** Anthropic is refusing the Claude *account* behind your
token (the `oauth_org_not_allowed` flag), not the token itself. Minting a
fresh `claude setup-token` doesn't help: the new token carries the same flag.
Despite the "organization" wording, this hits personal Pro/Max accounts too,
usually after a lapsed or duplicate subscription.

**Fixes:**

1. **On a company Claude plan:** ask the plan's admin to enable Claude Code
   access. It's a seat/plan setting on their end.
2. **On a personal plan:** check claude.ai billing for a lapsed or duplicate
   subscription. If everything looks fine, contact Anthropic support - there's
   no self-serve fix for this.
3. **Sidestep subscriptions entirely:** create an API key at
   console.anthropic.com and add it to your site repo with
   `gh secret set ANTHROPIC_API_KEY`, then delete the dead token with
   `gh secret delete CLAUDE_CODE_OAUTH_TOKEN`. The builders use the key
   automatically whenever no subscription token exists. This runs on metered
   billing against your Anthropic account.
4. **Or switch the site to Codex or Cursor** in Settings.
</Symptom>

<Symptom error="user cancelled MCP tool call (Codex)">
**What it means.** Codex asks for per-call approval on MCP tools by default,
and on a scheduled run there is nobody to approve - so every call comes back
"cancelled". It's an approval setting, not a sandbox or permission problem, so
no amount of sandbox widening fixes it.

**Fix.** The config entry needs `default_tools_approval_mode = "approve"` on
each MCP server. The shipped `.github/mcp-codex.toml` and the builder
container both set it already - this bites when a hand-written
`~/.codex/config.toml` entry is missing the line. See
[Use Codex instead](/docs/install-codex).
</Symptom>

<Symptom error="The queue is empty and stays empty">
**What it means.** Research hasn't run, or it ran and rejected everything
against the quality bar.

**Fix.** Ask your agent directly: `/seo-research`, or just "research keywords
for me". If it comes back with nothing, the difficulty ceiling is doing its
job - your domain isn't strong enough yet for the candidates it found. That's
information, not a bug. Adding a
[keyword data source](/docs/keyword-data) widens what it can evaluate.
</Symptom>

<Symptom error="Approved ideas sit there for days">
**What it means.** Publishing pace is one guide per UTC calendar day, always.
A queue of ten approved ideas takes ten days to drain by design.

**Fix.** None needed - this is intended. Tools are different: approving a
tool triggers a build immediately.
</Symptom>

## Search Console

<Symptom error="No GSC data after connecting">
**What it means.** Either the service account isn't actually on the property,
or the property string doesn't match its type.

**Fix.** Check both:

1. The service account's email is added as a user on the property.
   **Restricted** is enough - it's what the wizard tells you to pick.
2. `GSC_SITE_URL` matches the property type exactly: `sc-domain:example.com`
   for a domain property, `https://example.com/` **with the trailing slash**
   for a URL-prefix one.

First data lands within an hour of a correct setup. See
[Google Search Console](/docs/search-console) for the full walkthrough.
</Symptom>

<Symptom error="Traffic numbers look lower than Search Console shows">
**What it means.** Google's own reporting lags by a couple of days, and
DispatchSEO snapshots what the API returns at the time it asks.

**Fix.** None - the gap closes on its own. Compare like-for-like date ranges
before concluding something is wrong.
</Symptom>

## Pull requests

<Symptom error="The Merge button is a plain link instead of a button">
**What it means.** One-tap merge needs `GH_MERGE_TOKEN`. Without it the
dashboard degrades gracefully to a link that opens the PR on GitHub.

**Fix.** Add a GitHub token with repo scope as `GH_MERGE_TOKEN`, or keep
merging on GitHub - nothing else depends on it.
</Symptom>

<Symptom error="Auto-merge is on but PRs aren't merging">
**What it means.** Auto-merge only merges PRs that are labeled `seo` **and**
green. A failing check, or a missing label, leaves it alone on purpose.

**Fix.** Open the PR and look at the checks. If your repo requires reviews via
branch protection, auto-merge cannot satisfy that - either approve manually or
adjust the rule.
</Symptom>

## Get emailed when something breaks

Optional, two minutes, and strongly recommended if you picked **Automatic**
publish mode: in auto mode nobody opens the dashboard on a normal day, so the
email is what actually tells you a job broke. In semi-automatic mode you visit
weekly anyway, so the red banner already has you covered - set this up if
you'd rather not wait for your next visit.

<Steps>
  <Step title="Create a free Resend account">
    Emails go through [Resend](https://resend.com/signup) - free tier, no
    credit card, no domain setup needed for this.
  </Step>
  <Step title="Create an API key">
    Open [resend.com/api-keys](https://resend.com/api-keys), click **Create API
    Key**, name it `dispatchseo`, leave the defaults, and copy the key. It
    starts with `re_` and is shown only once.
  </Step>
  <Step title="Add two lines to .env">
    On the machine running DispatchSEO (on a VPS: over SSH, not your own
    computer), inside the `dispatchseo` folder. Replace both values. The first
    part checks you're in the right folder before writing anything:

```bash
[ -f start.sh ] &&
  echo "RESEND_API_KEY=re_PASTE-YOUR-KEY-HERE" >> .env &&
  echo "ALERT_EMAIL=you@example.com" >> .env &&
  sh start.sh ||
  echo "Wrong folder - run this inside the dispatchseo folder"
```

  </Step>
</Steps>

<Callout type="warning" title="ALERT_EMAIL has to match your Resend signup">
Alerts go out through Resend's built-in sender, which only delivers to its own
account's address. If you've verified a domain in Resend you can send from it
to any address - set `ALERT_EMAIL_FROM` to something like
`DispatchSEO <alerts@your-domain.com>` in `.env` as well.
</Callout>

From the next run onward, failures email you. No email means everything is
working.

## Docker reference

Every command runs inside the `dispatchseo` folder the install created.

| What you want | Command |
| --- | --- |
| See what's running | `docker compose ps` |
| App logs | `docker compose logs app` |
| Schedule logs | `docker compose logs cron` |
| Builder logs | `docker compose logs builder` |
| Follow logs live | `docker compose logs -f app` |
| Restart everything | `sh start.sh` |
| Stop everything | `docker compose down` |
| Delete everything, including data | `docker compose down -v` |

Cron failures ride the same banner and email rails as any other install, so
you don't have to watch the logs to notice a problem.

## Still stuck

Ask in the [Discord](/discord). Paste the failing job's log lines if it's a
scheduling issue, and say whether you're on Docker, a VPS, or the cloud
version - that's usually enough for someone to spot it. A thread in
[Discussions](https://github.com/NeoZi12/dispatchseo/discussions) is the better
home for a longer write-up, or for a bug that needs a fix in the repo.

Vulnerabilities go through
[private reporting](https://github.com/NeoZi12/dispatchseo/security/policy)
instead, not a public thread.
