# Troubleshooting

> Symptom first — what the wizard and the scaffolded app do when something is wrong, what causes each one, and the page that owns the fix.

Page: https://sdk.chatfuel.com/docs/troubleshooting
Markdown: https://sdk.chatfuel.com/docs/troubleshooting.md

Two things break in different ways here: the wizard, which runs once on your machine, and the app
it writes, which runs on a host. The symptoms below are grouped that way. Every one of them links
to the page that owns the detail.

## Check `/healthz` first [#check-healthz-first]

The app's server answers `/healthz` with its own view of the auth gate, uncached:

```json
{ "ok": true, "auth": "on" }
```

| `auth`          | What it means                                                                                                       | When you'd want it                  |
| --------------- | ------------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
| `on`            | `VITE_SUPABASE_URL` and `VITE_SUPABASE_ANON_KEY` are both set. Every request is checked against a Supabase session. | The app has its own sign-in.        |
| `off`           | Neither is set. There's no sign-in, and requests are fenced against the deployment's own workspace instead.         | One bot, one operator, no accounts. |
| `misconfigured` | Exactly one of the two is set. The gate fails closed and refuses everything.                                        | Never — it's the state to fix.      |

Read that before any other symptom on this page. The server also prints the mode on startup.

<Callout type="warn">
  A build/runtime mismatch never announces itself. `VITE_*` values are compiled into the browser
  bundle when you build; everything else is read by the server when it starts. Nothing fails at the
  moment the two disagree — it fails later, at the gate, and reads as an auth bug.
</Callout>

## The wizard [#the-wizard]

<Accordions>
  <Accordion title="It prints a Node version message and stops">
    The floor is Node 20.18.1, compared on all three parts — the HTTP stack the wizard uses isn't in
    every 20.x. Install a newer Node and run it again; nothing else needs installing by hand.

    The launcher that prints this is deliberately old-style JavaScript, so it parses on a Node too old
    to run the rest and can say so instead of throwing a syntax error. See [the
    CLI](/docs/reference/cli).
  </Accordion>

  <Accordion title="It refuses to write into the directory">
    Scaffolding into a directory that exists and isn't empty is refused outright — no merge, no
    overwrite. Pick a fresh one with `--dir <path>`, or use `--embed` to add the modules to a project
    that already exists.

    `--embed` is a different mode, not a flag on the same one: [Embed into an existing
    app](/docs/guides/embed-into-an-existing-app) covers what it appends and what it leaves alone.
  </Accordion>

  <Accordion title="--yes finished and there is no app">
    Every prompt has a non-interactive answer, but a missing or rejected `CHATFUEL_TOKEN` is not one of
    them. The wizard reads the token from the environment when it's there and plausible; when it isn't,
    a run with no questions ends with an error rather than a prompt nobody can answer.

    Export the token, or pass the flag the question wanted — [Running without
    prompts](/docs/guides/non-interactive) lists them. `npx @chatfuel/wizard doctor` reports what the
    wizard can see before it asks anything.
  </Accordion>

  <Accordion title="--yes did not deploy and did not push to GitHub">
    Both are interactive-only, by design and in both directions: they're skipped on `--yes`, on
    `--dry-run`, and whenever there's no TTY. A deployment is a public URL with somebody's Chatfuel
    token behind it, and a repository is their source code under their account; neither is something a
    non-interactive flag should be able to cause.

    Do them yourself afterwards: `npm run deploy` ([Vercel](/docs/deploy/vercel)) and `gh repo create --source . --push` ([Push to GitHub](/docs/guides/push-to-github)).
  </Accordion>

  <Accordion title="The trial step went to a plain checkout">
    A trial belongs to the Chatfuel account, not to a workspace, so every workspace after the first is
    answered `TrialNotAllowed`. That's the ordinary case, not a failure — the step falls back to the
    same offer minus the trial days, because a workspace with no plan has no AI either way.
    The prices are on [the pricing page](/pricing).
  </Accordion>

  <Accordion title="A step failed behind a corporate proxy">
    Node's own `fetch` ignores `HTTPS_PROXY` unless the process was started with a flag nobody types,
    and `ws` never reads it at all — so behind a proxy every call would go direct and time out, and a
    token check that times out reads as "your token is bad". The wizard and the app both route their
    outbound calls themselves instead: `https_proxy`, `HTTPS_PROXY`, `http_proxy`, `HTTP_PROXY`,
    `all_proxy` and `ALL_PROXY` are read in that order, `NO_PROXY` is honoured, loopback is always
    exempt, and the WebSocket relay tunnels through an agent of its own.

    What has to be reachable: `panel.chatfuel.com` always, `registry.npmjs.org` to install anything,
    `api.vercel.com` to deploy, `api.github.com` to push. The variables are listed with everything else
    the server reads in [Environments](/docs/deploy/environments).
  </Accordion>
</Accordions>

## The app [#the-app]

<Accordions>
  <Accordion title="A reload of /deals/board is a 404, but clicking to the same page works">
    The host isn't answering unknown paths with `index.html`. The router is path-based, so
    `/deals/board` is a real address that the host has to hand back to the app — the failure looks like
    a routing bug and isn't one.

    The bundled Node server already does this. On Vercel it's the last rewrite in `vercel.json`; on
    nginx, `try_files $uri /index.html`; on a static host, its SPA-fallback setting. [Picking a
    host](/docs/deploy) states the rule for each.
  </Accordion>

  <Accordion title="Every request answers ProxyAuthMisconfigured">
    Some of the Supabase variables are set and some are not. The gate fails closed rather than guessing
    which half you meant, and answers `500 ProxyAuthMisconfigured` on every proxied route.

    Set both `VITE_SUPABASE_URL` and `VITE_SUPABASE_ANON_KEY`, or neither. `/healthz` says which of the
    three states you're in; [Environments](/docs/deploy/environments) says where each variable has to be
    set on each host, and [Errors](/docs/reference/errors) has the code.
  </Accordion>

  <Accordion title="The sign-in screen renders but every request is AuthSessionRequired">
    Or the mirror image: nobody is ever asked to sign in, while the server rejects everything. Both mean
    the client was built with a different environment than the server is running with — a client built
    with Supabase against a server started without it, or a gated server serving a client built without
    it.

    `VITE_*` is compiled in at build time; everything else is read at runtime. You can't fix this at
    runtime, because the values are already in the bundle: rebuild with the values the server is running
    with. [Environments](/docs/deploy/environments) has the build-time/runtime split per host.
  </Accordion>

  <Accordion title="403 BotNotAllowed or 403 AccountScopeBlocked">
    The auth gate working as intended. `BotNotAllowed` means a request named a bot this session — or
    this deployment — may not open. `AccountScopeBlocked` means the query asked an account-wide question
    under `currentUser` that would list every tenant; ask about a bot instead.

    [The auth gate](/docs/concepts/auth-gate) explains the two fences, and
    [Errors](/docs/reference/errors) has the exact conditions for each code.
  </Accordion>

  <Accordion title="A removed member could still act for half a minute">
    Gate answers are cached 30 seconds per token, so HTTP access ends within that window and no later
    than the session's own expiry. A WebSocket is checked at connect and keeps working until it drops,
    so an already-open subscription outlives the removal by longer.

    That window is deliberate — the alternative is a round trip to Supabase on every proxied call. [The
    auth gate](/docs/concepts/auth-gate) says what it does and does not cover.
  </Accordion>

  <Accordion title="Subscriptions drop every few minutes on Vercel">
    A serverless function has a duration limit — the app ships `maxDuration: 300` and 300 seconds is
    the Hobby ceiling — and the relay socket closes with it. The close is non-fatal, so the client
    reconnects through a fresh function and refetches what the socket was feeding; nothing is lost.

    On your own server or in Docker the socket is unbounded. [Vercel](/docs/deploy/vercel) covers the
    trade.
  </Accordion>

  <Accordion title="The app is served from /app/ and every script 404s">
    `VITE_BASE_PATH` (build) and `BASE_PATH` (runtime) have to carry the same value. The first rewrites
    the asset URLs inside `index.html`; the second tells the server which prefix is the app. A client
    built for `/app/` and served at the root asks for files that aren't there.

    Vercel serves from the domain root and supports no sub-path at all. [Serving from a
    sub-path](/docs/deploy/sub-path) has both variables and the failure in each direction.
  </Accordion>

  <Accordion title="/admin says there is no panel here">
    `ADMIN_PASSWORD` is unset, so the proxy never claims those paths and the host answers its own 404 —
    which the app reads, correctly, as "this deployment has no panel". Set the variable and restart.

    It has to be at least 16 characters; a shorter one leaves the routes mounted and refusing, with
    `AdminMisconfigured`. The panel is never in the navigation rail either, on purpose — the URL is the
    whole way in. [The admin panel](/docs/guides/admin-panel) and [the Admin
    module](/docs/modules/admin) have the rest.
  </Accordion>
</Accordions>

## Not on this page [#not-on-this-page]

<Cards>
  <Card title="Limits that are not bugs" href="/docs/troubleshooting/limits" description="Behaviour that looks broken, is not, and will not change — because the Chatfuel API cannot answer the question underneath it." />

  <Card title="Getting help" href="/docs/troubleshooting/support" description="Where to ask, what to include, and what to redact before you paste it anywhere." />

  <Card title="Errors" href="/docs/reference/errors" description="Every error code the stack can produce, code by code, with what causes it." />
</Cards>
