EveryAppEveryApp

Troubleshooting by symptom

What a symptom from everyapp, the Gateway or an app usually means, how to confirm it (status --json fixes, --verbose, the log file) and the fix, with a link to each error code's entry.

This page starts from what a person sees or says, not from a code. Each entry says what it usually is, how to confirm it, and what to run. A code's own entry is on Error codes; the --json event shapes are on The --json protocol.

Two things fix most problems: run the same command again (setup and deploys resume where they stopped: every step looks at what is there first), and run pnpm everyapp status --json (its data.fixes lists what is off and the command for each).

How a problem is reported

  • A person sees a one-line headline, its detail dim under it, then Fix: … and Run: <command> (To go ahead anyway: <command> when the command takes something over). Problems go to stderr. packages/cli/src/lib/output/events.ts.
  • With --json, stdout is NDJSON. A failure is the last event: error (code, message, retryable, data.fix, data.rerun, data.rerunNote, data.url, data.authorize) or action_required (action, data.url, data.code, data.rerun or data.choices, data.waiting, data.needsTerminal: true when only a person at a terminal can do the rerun, so an agent hands data.rerun to a person instead of running it again; under --json it would stop here again). Exit code 0 when done, 2 when the run stopped on an action (do it, run the same command again), 1 after an error. An error with data.authorize: true carries a takeover flag (--force-dns, --allow-delete-durable-object-data, --allow-delete-workflows, --enable-email-routing, --yes, setup's --allow-downgrade): show it and ask the person before running it. packages/cli/src/lib/errors.ts, packages/cli/src/lib/output/report.ts.
  • An error raised after a migration changed a database appends The database stays migrated. To roll it back: pnpm everyapp db restore … and carries data.rollbackCommand (cf's own d1 time-travel restore).
  • --verbose adds the bookkeeping steps and progress, the error's code (Error code: …), an unexpected error's stack frames, the output of the children everyapp runs (echoed dim to stderr as it comes), and runs cf deploy attached to the terminal, where cf shows its output and asks its own questions (a Worker changed in the dashboard, a hostname another Worker serves). The warning CUSTOM_DOMAIN_OWNER_UNCHECKED shows only with --verbose.
  • The log file: the children everyapp runs itself (pnpm, a manifest's install or build command, git, the Gateway's release build) write to $TMPDIR/everyapp/<command>-<time>-<pid>.log (mode 0600), never to stdout. When one fails the error says what ran, its last 20 lines and Full log: <path> (data.command, data.exitCode, data.tail, data.log). cf's own output never goes there: the runner keeps it in memory and a failure carries its last lines (BUILD_FAILED prints cf build's last lines:). packages/cli/src/lib/childLog.ts, packages/cli/src/lib/app/buildApp.ts.
  • Codes that come from the reporting rather than one raise site, all in packages/cli/src/lib/output/report.ts: CANCELLED (a person declined a prompt; shown dim, exit 1), UNEXPECTED (a bug: This is a bug in everyapp, not something you did., with data.stack), NETWORK_ERROR (retryable), SYSTEM_ERROR, CF_TIMEOUT (retryable), CF_OUTPUT, INVALID_MANIFEST, and CF_<KIND> for a cf failure nothing translated (CF_AUTH, CF_NOT_FOUND, CF_ALREADY_EXISTS, CF_ABORTED, CF_STRICT_CONFLICT, CF_SUBDOMAIN_MISSING, CF_EMAIL_UNVERIFIED, CF_R2_NOT_ENABLED, CF_D1_ID_REJECTED, CF_UNKNOWN; the kinds are in packages/cli/src/lib/cf/errors.ts). A cf failure of the free_plan kind that no raise site translated never becomes CF_FREE_PLAN: reportFailure intercepts it first and reports action_required upgrade_workers_paid (Cloudflare refused, for the Free plan: <cf message>, EveryApp needs Workers Paid ($5 a month, then usage). Subscribe, then run this command again., data.url https://dash.cloudflare.com/?to=/:account/workers/plans, data.rerun the same command). CF_ABORTED means cf asked a question nobody could answer and printed Aborted.: run it at a terminal, or pass the flag the command offers.

What status reports

The report line by line, the done.data field behind each line and the loop to run until fixes is empty: everyapp status: the report and its fixes.

pnpm everyapp status --json reads the Gateway's D1, cf and the checkout, nothing over HTTPS. data.fixes[] is { code, severity, message, run, note?, fix? } (run is null when there is nothing to run; note is the parenthesis after the command in the table below, e.g. updates it now; packages/cli/src/commands/status/index.ts, fixData); data.apps[] has each app's status (pending, binding, active, disabled), bound (the live Gateway binds its Worker), deployedBy, email and setup ({ ready, settings, unmet, groups, command }: what its manifest needs set and whether it is; null when it declares nothing, is still pending or its Worker is gone); also data.inSync, data.upToDate, data.versionId against data.recordedVersionId, data.certificates[] ({ host, ready }), data.tls, data.leases[], data.owner, data.mode, data.release.version, data.checkout.version. The fix codes (packages/cli/src/commands/status/problems.ts; they are status codes, not error codes):

Code Headline Run
CHECKOUT_BEHIND This checkout is behind the live Gateway (deploys from here are refused) git pull
GATEWAY_OUTDATED Your every-app folder has a newer Gateway than the one online (severity info: listed last, not a problem to fix) pnpm everyapp update (updates it now)
GATEWAY_NOT_INSTALLED Setup stopped before it recorded the Gateway's install pnpm everyapp setup (asks for the hostname again)
GATEWAY_NOT_UPLOADED The Gateway's install stopped before its first upload pnpm everyapp setup
GATEWAY_CHANGED The Gateway Worker was changed outside everyapp pnpm everyapp setup (redeploys the live release, asking first)
BINDINGS_DIFFER The live Gateway doesn't serve <apps> or The live Gateway's apps or hostnames are out of date pnpm everyapp setup
NO_OWNER No owner yet, so nobody can sign in pnpm everyapp setup
APP_WORKER_MISSING <app>'s Worker no longer exists pnpm everyapp deploy from the app's directory, or pnpm everyapp delete <app>
APP_NOT_BOUND The Gateway can't bind <app> (its Worker isn't named the way the Gateway names it) pnpm everyapp delete <app>
APP_NOT_UPLOADED <app> was registered, but none of its deploys finished pnpm everyapp deploy from the app's directory, or pnpm everyapp delete <app>
APP_BINDING <app> goes live on its first visit to https://<host> (settling) none
APP_NEEDS_SETUP <app> needs setup (a required setting of its manifest isn't set; each unmet setting with its why under it) pnpm everyapp configure <app> (the one command that opens it: asks for each unmet setting, which member of an any-of group, then offers the optional ones)
SETTINGS_UNKNOWN Couldn't list the Workers' secrets (Cloudflare said: <error>. Whether the apps' settings are set isn't known.) none
EMAIL_INBOUND_UNDECLARED <app> has an inbound address, but its config doesn't turn inbound mail on declare email: { inbound: true } and deploy, or pnpm everyapp email unset inbound
EMAIL_ROUTE_PENDING <app> doesn't get mail at <address> yet pnpm everyapp deploy from the app's directory
EMAIL_ROUTE_MISSING <app> doesn't get mail at <address> (its rule is gone, off, or Email Routing is off) pnpm everyapp deploy from the app's directory
EMAIL_ROUTES_UNKNOWN Couldn't check the apps' Email Routing rules none
CERTIFICATES_UNKNOWN Couldn't list <zone>'s certificates none
CERT_PENDING No certificate yet for <hosts> (settling) none
ACCESS_UNKNOWN Couldn't check whether Cloudflare Access is on (Cloudflare said: <error>.: the Gateway Worker's settings couldn't be listed) none
DEPLOY_RUNNING <A deploy of app from who on machine, started 2m ago> is running (settling) none

The user docs list

Every entry of the user docs' Troubleshooting page, in its order, and where it goes deeper here.

User docs heading Here
Subscribe this account to Workers Paid Workers Paid
Add a domain to this Cloudflare account No domain in the account
Finish activating your domain on Cloudflare Zone not active
Choose a workers.dev subdomain for this Cloudflare account Workers subdomain
Your address already points somewhere else Address already points somewhere
The Gateway build fails with connect ECONNREFUSED 127.0.0.1 Build fails with connect ECONNREFUSED 127.0.0.1 on Linux
Open the setup link above to create the owner account Owner link lost or expired
Use the newer setup link, or get a new one Owner link lost or expired
A new address doesn't load yet A new address does not load
An app in your Gateway says Needs deploy, Needs setup, Deploying or Disabled App states
The schema has changes no migration covers Schema drift
This checkout's Gateway is older than the live one Downgrade refused
Your every-app folder has a newer Gateway than the one online Gateway outdated or upgrading during a deploy
everyapp needs Node.js 22.18.0 or newer Node too old
ERR_PNPM_LOCKFILE_CONFIG_MISMATCH Frozen install refused
Command "everyapp" not found Not in the checkout
No everyapp.config.ts in this directory Not in the app directory
Strange errors after using npx npx everyapp
Still stuck When to escalate

Setup and login

Workers Paid

  • Symptom: action_required with action: "upgrade_workers_paid". From setup, before anything is created: Subscribe this account to Workers Paid, detail It's on the Free plan. EveryApp needs Workers Paid ($5 a month, then usage): on Free, the Gateway stops once D1 hits its daily limit. From an upload cf refused: Subscribe this account to Workers Paid with Cloudflare refused the upload on the Free plan. (data.cloudflare holds cf's message), or Cloudflare refused, for the Free plan: …. A softer form is the warning WORKERS_PAID_UNVERIFIED (Couldn't confirm this account is on Workers Paid): setup goes on.
  • Cause: cf accounts subscriptions get names a Workers Free subscription (packages/cli/src/lib/cf/plan.ts), or cf's upload error matched Workers Paid|paid plan (a match packages/cli/src/lib/cf/errors.ts marks unverified against a live account, like the subdomain one). A plan that reads as neither is only warned about; one cf couldn't read at all is a setup.plan skip step (Couldn't read this account's plan; Cloudflare refuses the upload if it's on Free, shown with --verbose). Setup checks the plan only for a new install (if (!live)).
  • Confirm: the three forms carry different data. From setup's own check (setup.plan): data.url https://dash.cloudflare.com/<account id>/workers/plans and data.plan: "free". From an upload cf refused: the same URL, with data.workerName and data.cloudflare (cf's message) and no plan. From the report-level form (Cloudflare refused, for the Free plan: …): data.url https://dash.cloudflare.com/?to=/:account/workers/plans, no plan.
  • Fix: a person subscribes at data.url (user docs), then the same command again (data.rerun). Exit 2 meanwhile. "Nothing was created" holds for setup's own check only: it runs before the install. After a refused upload the Gateway's D1 and KV and the install row already exist (deployOrRepairGateway runs ensureGatewayResources and writes the row before cfDeploy), and setup resumes from them. Source: packages/cli/src/commands/setup/runSetup.ts, packages/cli/src/commands/setup/deployGateway.ts, packages/cli/src/lib/cf/deploy.ts, packages/cli/src/lib/gateway/apply.ts, packages/cli/src/lib/output/report.ts.

No domain in the account

  • Symptom: action_required add_domain: Add a domain to this Cloudflare account, detail EveryApp needs one. Buy one in the dashboard (about $10 a year for a .com, ready in minutes), or point a domain you own at Cloudflare's nameservers., data.url https://dash.cloudflare.com/<account id>/domains/overview. With --domain under a domain the account doesn't hold: ZONE_NOT_FOUND, <domain> isn't under a domain in this Cloudflare account, listing This account's domains: … (data.zones, data.url).
  • Cause: cf zones list --account-id <id> returns no zone (or none named by the hostname or its parents; the zone is the longest of the name and its parents the account has). If the login sees several accounts, the one chosen may be the wrong one: see Several Cloudflare accounts.
  • Confirm: cf auth whoami for the accounts; cf zones list --account-id <id>.
  • Fix: buy or add a domain (user docs); for ZONE_NOT_FOUND, use a name under one of them, or add <domain>'s domain to Cloudflare (--domain apps.<zone>). Then the same command. Source: packages/cli/src/commands/setup/runSetup.ts (activeZones), packages/cli/src/commands/setup/deployGateway.ts, packages/cli/src/lib/domains/zones.ts.

Zone not active

  • Symptom: action_required add_domain: Finish activating <zone> on Cloudflare, detail Its nameservers don't point at Cloudflare yet., data.zone, data.status, data.url https://dash.cloudflare.com/<account id>/<zone>. From commands that resolve a zone another way (an email address's domain): ZONE_NOT_ACTIVE, The Cloudflare zone <zone> is <status>, not active, fix finish its nameserver setup (Cloudflare dashboard → <zone> → Overview).
  • Cause: the zone's status isn't active (pending, initializing, …): the registrar still points at other nameservers. Custom Domains can't be created in a zone that isn't active.
  • Confirm: cf zones list --name <zone> --account-id <id> shows the status.
  • Fix: finish the nameserver step (user docs), wait for Cloudflare's "active" email, run the same command. Source: packages/cli/src/commands/setup/deployGateway.ts, packages/cli/src/lib/domains/zones.ts.

Workers subdomain

  • Symptom: the account has no workers.dev subdomain yet: action_required register_workers_subdomain: Choose a workers.dev subdomain for this Cloudflare account, detail Cloudflare needs one before an account's first Worker. Apps never use it.; data.url is the link cf printed, else https://dash.cloudflare.com/<account id>/workers/onboarding.
  • Cause: cf deploy failed with cf's subdomain_missing kind (its message matches workers.dev subdomain|/workers/onboarding; packages/cli/src/lib/cf/errors.ts marks that match unverified against a live account): the account has never had a Worker.
  • Confirm: the Gateway Worker doesn't exist yet; the install row was written before the upload, so status says GATEWAY_NOT_UPLOADED.
  • Fix: open data.url, pick any name, run the same command; setup resumes from the install row without --domain. Source: packages/cli/src/lib/cf/deploy.ts, packages/cli/src/lib/gateway/apply.ts.

Several Cloudflare accounts

  • Symptom: action_required choose_account. Setup with no Gateway anywhere: Choose the Cloudflare account to install in, <email> can use N accounts. Run one of:; any command when the login sees a Gateway in several accounts: Choose which Gateway to use. data.choices[] is one command per choice ({ accountId, accountName, label, rerun }, plus gatewayUrl for a Gateway); data has no rerun key at all (the error's rerun is null and reportFailure adds rerun only when it is set). Related errors: ACCOUNT_NOT_ACCESSIBLE (<email> can't use Cloudflare account <id>, then the accounts it can use), ACCOUNT_REQUIRED (This Cloudflare login can't list any accounts), GATEWAY_NOT_FOUND (Cloudflare account <id> has no Gateway naming where the login's Gateway is; or No Gateway in <account>, the only account this login sees with rerun pnpm everyapp setup --domain <hostname>), GATEWAY_URL_MISMATCH (The Gateway here is <found host>, not <requested host>), INVALID_GATEWAY_URL (before any lookup: --gateway must be a full URL, not "<value>", For example https://apps.example.com.; or EVERYAPP_GATEWAY_URL must be a full URL, not "<value>", fix set it to https://<value>, or unset it), and action_required install_gateway (status: No Gateway in <account>'s Cloudflare account (logged in as <email>), rerun pnpm everyapp setup; deploy: Install a Gateway first: <account> has none yet, Apps deploy behind one. Setup needs a person once, to create the owner account., rerun pnpm everyapp setup --domain apps.<zone> (the first active zone; --domain <hostname>, literally, when the account or its zones couldn't be read; --account-id <id> is appended only when the deploy itself had --account-id), data.accounts, data.domain).
  • Cause: discovery asks every account the login lists for the Gateway's D1 database (cf d1 list --name every-app-gateway per account); an account cf refuses or doesn't know counts as having none. A CI token scoped to one account lists none, so CLOUDFLARE_ACCOUNT_ID names it.
  • Confirm: cf auth whoami lists the accounts; data.accounts / data.choices.
  • Fix: run one of data.choices[].rerun; or pass --account-id <id> / set CLOUDFLARE_ACCOUNT_ID; wrong login: pnpm everyapp login. Source: packages/cli/src/lib/cf/account.ts, packages/cli/src/lib/gateway/discover.ts, packages/cli/src/lib/gateway/connect.ts, packages/cli/src/commands/deploy/steps/offerGateway.ts.

Login failed or token rejected

  • Symptom: CLOUDFLARE_LOGIN_FAILED: Cloudflare login didn't complete (rerun pnpm everyapp login), or under --json Cloudflare login timed out after 5m (retryable). CLOUDFLARE_TOKEN_REJECTED: Cloudflare didn't accept CLOUDFLARE_API_TOKEN, It may be mistyped, expired or revoked., A token needs Account › Workers Scripts › Edit, Account › D1 › Edit, Account › Workers KV Storage › Edit, Zone › Zone › Read and Zone › DNS › Read. Unattended with no way to log in: action_required cloudflare_login, Log in to Cloudflare with pnpm everyapp login, Unattended? Set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID instead.
  • Cause: cf auth whoami says authenticated: false after the login ran; or CLOUDFLARE_API_TOKEN is set and cf isn't authenticated with it (the CLI never logs in while the variable is set).
  • Confirm: with --json, the device login is action_required cloudflare_login with data.url, data.code, data.expiresAt (5 minutes), data.waiting: true, data.waitingFor: "Cloudflare login": hand the link and code to a person and keep reading. Check echo $CLOUDFLARE_API_TOKEN is unset when a person is meant to log in.
  • Fix: approve the device link within 5 minutes; or set a valid token, or unset CLOUDFLARE_API_TOKEN and run pnpm everyapp login; CI: a token with the five permissions the message names, plus CLOUDFLARE_ACCOUNT_ID. Source: packages/cli/src/lib/cf/account.ts.
  • Symptom: a person lost the Open this link to create the owner account link, it says it expired, or status reports NO_OWNER (No owner yet, so nobody can sign in, Apps still deploy.). Under --json, the link is action_required claim_owner with data.url (https://<gateway>/setup#token=est_…), data.expiresAt, data.replaced, data.waiting: true, data.waitingFor: "the owner account (up to 15 minutes)". After 15 minutes unopened (polling the link's row every 5 s) the run ends with a last action_required claim_owner: Open the setup link above to create the owner account, Nobody opened it within 15m. It still works for <time>; running setup before it's opened replaces it. (exit 2). While the link is shown, setup probes https://<gateway>/ (checkReachable) and warns GATEWAY_UNREACHABLE (<host> doesn't answer over HTTPS yet, … The link below works as soon as it does., data.kind, data.reason) or ACCESS_IN_FRONT (Cloudflare Access protects <host>, Whoever opens the setup link signs in to Access first.). In CI, or piped without --json, no link is shown at all: warning OWNER_NOT_CLAIMED, The Gateway has no owner yet, Apps deploy without one. To create it, run setup at a terminal with this Cloudflare login.; its data is rerun pnpm everyapp setup, rerunNote at a terminal, needsTerminal: true (an agent hands the rerun to a person instead of running it).
  • Cause: a link works once and expires 24 hours after it was minted; minting a new one expires every unused earlier one (data.replaced: true: The link shown before no longer works.). Only the SHA-256 of the secret is in D1 (owner_setup_tokens); the secret is in the URL fragment.
  • Confirm: pnpm everyapp status --json: data.owner null, data.claimed false, a NO_OWNER fix.
  • Fix: pnpm everyapp setup again, at a terminal (it opens the browser) or with --json relayed by an agent on the same machine; open the new link; create the owner. Source: packages/cli/src/commands/setup/runSetup.ts (ensureOwner), packages/cli/src/lib/gateway/setup-link.ts.

Setup stopped halfway

  • Symptom: status says GATEWAY_NOT_INSTALLED, GATEWAY_NOT_UPLOADED, GATEWAY_CHANGED or BINDINGS_DIFFER (table above); an app command fails with GATEWAY_NOT_INSTALLED (The Gateway at https://<host> isn't finished installing, Setup stopped before its first upload., or The Gateway's install didn't finish, Setup stopped before it recorded the install.); or an unattended run stops with action_required rerun_interactive: The Gateway was changed outside everyapp; confirm in a terminal, Its live version wasn't deployed by everyapp, and Cloudflare won't replace it unattended. Nothing changed. (rerunNote in a terminal; data.workerName, data.versionId, data.needsTerminal: true; the same for an app's Worker). When cf itself refuses the upload in strict mode, the same action comes from the runner: <worker> was changed outside everyapp; confirm in a terminal, It was last changed in the Cloudflare dashboard or through the API, so cf won't overwrite it unattended., rerunNote in a terminal, and confirm cf's prompt, data.needsTerminal: true. data.needsTerminal tells an agent to hand the rerun to a person instead of running it again. A run that got as far as cf's upload and was declined there ends with DEPLOY_NOT_UPLOADED: cf finished without uploading a new version of <worker>, Was one of its questions answered no? The version that was serving still is. Every command that reads the Gateway (status, configure, secret list|set|unset, email show|set|unset, db query without --local, db restore, delete, access, mcp on|off|list) logs in to Cloudflare first (under --json the device login is action_required cloudflare_login: Login failed or token rejected) and then needs an installed Gateway: with none in any account the login sees, status ends with action_required install_gateway (rerun pnpm everyapp setup, rerunNote installs one, data.accounts) and the others with GATEWAY_NOT_FOUND (No Gateway in <account>, the only account this login sees, rerun pnpm everyapp setup --domain <hostname>); only deploy offers to install one. A Gateway whose install row is missing, or whose row never recorded its first upload, is GATEWAY_NOT_INSTALLED for every command but status (the two messages above); status fails on neither: a missing row ends done with data.installed: false and the GATEWAY_NOT_INSTALLED fix, a row with no first upload with data.installed: true and the GATEWAY_NOT_UPLOADED fix. packages/cli/src/lib/gateway/connect.ts (existingGateway, connectToGateway, requireInstall), packages/cli/src/lib/gateway/discover.ts (requireGateway).
  • Cause: setup writes the install row (_everyapp_install) before its first upload (live_version_id null) so a stop resumes from it; a Worker last changed in the dashboard or through the API (or by an interrupted deploy that never recorded itself) serves a version the row doesn't name, and cf's strict mode won't replace it unattended.
  • Confirm: status --json: data.installed, data.versionId against data.recordedVersionId, data.inSync, data.liveApps against data.desiredApps.
  • Fix: pnpm everyapp setup again (resumes; a repair redeploys with the registry's apps). For rerun_interactive, run the same command in a terminal and answer yes (with --verbose, cf asks its own prompt too). Step by step for an agent installing the Gateway: packages/cli/skills/everyapp/setup/gateway.md (in an app, .claude/skills/everyapp/setup/gateway.md). Source: packages/cli/src/commands/status/problems.ts, packages/cli/src/lib/gateway/install.ts, packages/cli/src/lib/gateway/connect.ts, packages/cli/src/lib/cf/deploy.ts (assertReplaceable).

Downgrade refused

  • Symptom: DOWNGRADE_REFUSED: This checkout's Gateway (<version>) is older than the live one (<version>), or with the same version This checkout's Gateway (<version> from <Mon D>) is older than the live one (from <Mon D>). data.live, data.candidate. status says CHECKOUT_BEHIND (This checkout has <v>; <live> is live., Deploys from here are refused until it catches up.).
  • Cause: the checkout's Gateway sources are earlier in the Gateway's history than the live release: compared by the newest migration's when, then by when the sources were last committed, never by version alone. Someone ran setup or a deploy from a newer checkout. Every app command checks this before it registers anything, since a rebind deploys this checkout's Gateway.
  • Confirm: status --json: data.upToDate false, data.checkout.version against data.release.version, a CHECKOUT_BEHIND fix.
  • Fix: git pull (and pnpm install), then the same command (rerunNote: "after that"). Only setup takes --allow-downgrade, offered with data.authorize: true: ask the person first; it puts an older Gateway back on purpose. Source: packages/cli/src/lib/gateway/apply.ts (downgradeRefused, olderThanInstalled), packages/cli/src/lib/gateway/release.ts (compareReleaseOrder).

Lease held by another deploy

  • Symptom: a progress line Waiting for a deploy of <app> to finish (<email> on <machine>, started 1m ago) for up to 5 minutes, then LEASE_HELD: A deploy of <app> is still running after 5m, It's from <email> on <machine>. If it stopped without cleaning up, it's released within <time>. (or Couldn't take hold of <the Gateway|app> after 5m); retryable, rerunNote: "once it's done (pnpm everyapp status shows it)", data.holder. From a deploy's bind step the message adds This app's upload is kept; deploying again binds it and makes it live. LEASE_LOST: Stopped: this run lost its hold on <target> (its hold expired mid-run; retryable). Warning LEASE_HEARTBEAT: a renewal failed (Can't reach Cloudflare to keep hold of <target>).
  • Cause: deploys are serialized by a row in _everyapp_deploy_lease in the Gateway's D1 (gateway, or app:<worker>). A lease expires 3 minutes after its last renewal and is renewed every 30 s, so a deploy that died frees it within 3 minutes; a live deploy holds it as long as it runs (5–10 minutes is normal). Nothing takes a live lease over.
  • Confirm: status --json data.leases[] (purpose, app, deployer, machine, acquiredAt, expiresAt); the DEPLOY_RUNNING fix.
  • Fix: wait for it, then run the same command. Source: packages/cli/src/lib/gateway/lease.ts, packages/cli/src/commands/appGateway.ts.

Node too old

  • Symptom: UNSUPPORTED_RUNTIME: everyapp needs Node.js 22.18.0 or newer; this is <version>, The Cloudflare CLI loads cloudflare.config.ts with Node's built-in TypeScript support., fix install Node 22 or 24, then run the command again. Or NODE_TOO_OLD: <pnpm|npm|yarn> runs this project's tools on Node.js <version>; cf needs 22.18.0 or newer, everyapp runs on <version>, but something pins this project's (a Volta volta.node in package.json, pnpm's use-node-version). A raw Unknown file extension ".ts" from a build means the same thing reached cf.
  • Cause: bin/cli.ts refuses a Node below 22.18.0 before any command runs. Inside an app, the package manager may run tools on another Node (a Volta pin, pnpm's use-node-version), which cf build / cf dev would use.
  • Confirm: node -p process.versions.node; in the app's directory pnpm exec node -p process.versions.node; look for volta.node in package.json and use-node-version in .npmrc.
  • Fix: install the LTS from nodejs.org and open a new terminal; raise the pin (volta pin node@22, for Volta), then the same command. Source: packages/cli/src/lib/runtime.ts, packages/cli/src/lib/cf/toolchain.ts, packages/cli/src/bin/cli.ts.

Bun

  • Symptom: UNSUPPORTED_RUNTIME: everyapp runs on Node.js, not Bun, The Cloudflare CLI can't load cloudflare.config.ts under Bun., rerun the same command with Node.js 22.18.0 or newer.
  • Cause: process.versions.bun is set: everyapp was started with bun or bunx.
  • Confirm: the command line the person ran starts with bun or bunx; pnpm everyapp --version from the checkout runs under Node and prints a version instead of this error.
  • Fix: run it with Node (pnpm everyapp … from the checkout). Source: packages/cli/src/lib/runtime.ts.

Frozen install refused

  • Symptom: pnpm install in the EveryApp checkout fails before any everyapp command runs: ERR_PNPM_LOCKFILE_CONFIG_MISMATCH Cannot proceed with the frozen installation. The current "overrides" configuration doesn't match the value found in the lockfile (another lockfile setting can stand in for "overrides"), then pnpm's own hint Update your lockfile using "pnpm install --no-frozen-lockfile".
  • Cause: pnpm ran frozen (--frozen-lockfile, or CI set, which turns it on; agents often add the flag themselves) and what it computes for overrides or settings from the checkout's pnpm-workspace.yaml and package.json differs from what the lockfile recorded. What differed in the onboarding loop's run isn't confirmed: a git archive of main installs frozen under pnpm 10.34.5 (the version the root package.json pins in packageManager), so a zip download is not the trigger by itself; a pnpm other than the pinned one is the usual cause.
  • Confirm: pnpm --version against packageManager in the root package.json; env | grep ^CI=; whether the command line had --frozen-lockfile.
  • Fix: pnpm install without --frozen-lockfile and with CI unset (env -u CI pnpm install); with an old pnpm, npm install -g pnpm first (pnpm 10 then switches to the pinned version by itself). No everyapp code is involved: the launcher never runs when the install fails.

Not in the checkout

  • Symptom: CHECKOUT_MISSING: everyapp has to run from a checkout of the EveryApp repository, This copy is installed in <dir>, outside any checkout., fix in your checkout, run pnpm everyapp <args>.
  • Cause: nothing is published to npm while the repository is the distribution; the starter template, the packages a new app links, and the Gateway build come from the checkout the running everyapp was built in. The checkout root is the directory holding both apps/every-app-gateway/scripts/build-release.ts and packages/cli/package.json.
  • Confirm: ls apps/every-app-gateway/scripts/build-release.ts packages/cli/package.json in the directory the person thinks is the checkout; the error's This copy is installed in <dir> names where the running everyapp lives.
  • Fix: cd into the every-app clone and run pnpm everyapp … there. Source: packages/cli/src/lib/checkout.ts.

Not in the app directory

  • Symptom: everyapp dev, deploy, db, db restore (and create, after it wrote the app): MANIFEST_NOT_FOUND, No everyapp.config.ts in this directory, fix run this from the app's directory (loadEveryAppManifest(cwd); dev loads the manifest the same way, so it says the same). everyapp email … without --manifest: the same code with fix run this from the app's directory, or pass --manifest <path>. No manifest at <path> is only what email --manifest <path> says when the file named isn't there (the default wording of loadEveryAppManifestFile). A directory with a manifest but no Worker config: MISSING_CLOUDFLARE_CONFIG. From everyapp dev, only when the manifest has no dev command: No cloudflare.config.ts in this directory, EveryApp apps build their Worker in cloudflare.config.ts with everyAppWorker() from @every-app/sdk/cloudflare., data.fix copy cloudflare.config.ts from starter. From everyapp deploy (not a release deploy): No cloudflare.config.ts in this app, EveryApp apps build their Worker in a cloudflare.config.ts at the app root: followed by the defineConfig / everyAppWorker({ manifest, entrypoint, ctx }) skeleton to copy and starter has the full setup (package.json, vite.config.ts).
  • Cause: deploy, dev, secret set of an app secret, email, db run in the app's directory (where everyapp.config.ts is: what everyapp create <name> made, or a clone of the app); configure <app>, secret set --app <id> and secret list <app> work from anywhere.
  • Confirm: ls everyapp.config.ts cloudflare.config.ts in the current directory.
  • Fix: cd into the app's directory and run it again; for a missing Worker config, copy the skeleton from the message (or starter/). Source: packages/cli/src/lib/manifest.ts, packages/cli/src/lib/app/cloudflareConfig.ts, packages/cli/src/commands/dev/index.ts, packages/cli/src/commands/deploy/deployApp.ts, packages/cli/src/commands/email/index.ts.

npx everyapp

  • Symptom: a person ran npx everyapp … and gets commands that don't exist, a different --version, or CHECKOUT_MISSING.
  • Cause: npx fetches whatever everyapp is on npm, which is not what this repository runs (publishing is paused; packages/cli/src/lib/checkout.ts and .github/RELEASE.md), and that copy isn't inside a checkout.
  • Confirm: pnpm everyapp --version in the checkout against npx everyapp --version; which everyapp or the shell history shows what was run.
  • Fix: always pnpm everyapp … from the every-app clone. Compare pnpm everyapp --version with what the person ran.

Gateway build or install failed

  • Symptom: GATEWAY_BUILD_FAILED: Building the Gateway failed, <pnpm …> exited with code <N>. Its last lines: (or was stopped by <signal>), the child's last lines indented, Full log: <path>, fix fix the build error above, then run this again. INSTALL_FAILED: Installing dependencies with pnpm failed (a manifest's own install command: Installing dependencies failed). GIT_FAILED: Initializing the app's git repository failed (create). COMMAND_FAILED is the default code of a logged child that names no other. A manifest's build command failing is BUILD_FAILED (Build failed), through the same runner.
  • Cause: the child everyapp ran exited non-zero. Setup builds the Gateway from the checkout in two children (pnpm --filter @every-app/sdk build at the root, then pnpm run build:release in apps/every-app-gateway; progress Building the Gateway from this checkout (usually 1–2m), step gateway.release, log name gateway-build-<time>-<pid>.log). pnpm 10 and later block dependency build scripts unless approved, and pnpm 11 exits 1 with ERR_PNPM_IGNORED_BUILDS in this non-interactive install; the Gateway release ships an allowBuilds allowlist in its pnpm-workspace.yaml for that.
  • Confirm: data.log (the full output), data.tail (the last 20 lines), data.exitCode, data.command; with --verbose the child's output is echoed dim to stderr as it runs.
  • Fix: fix the error in the log (a type error in the checkout, a missing dependency after git pull without pnpm install, a blocked build script), then the same command. Source: packages/cli/src/lib/childLog.ts, packages/cli/src/lib/gateway/checkoutRelease.ts, packages/cli/src/lib/package-manager.ts, packages/cli/src/lib/git.ts, packages/cli/src/lib/app/buildApp.ts.

Build fails with connect ECONNREFUSED 127.0.0.1 on Linux

  • Symptom: GATEWAY_BUILD_FAILED (setup's pnpm run build:release) or BUILD_FAILED (an app's cf build) on Linux, the log ending in Error: connect ECONNREFUSED 127.0.0.1:<port> from @tanstack/start-plugin-core/dist/esm/prerender.js. macOS never shows it.
  • Cause: an everyapp older than 2026-10-24. A TanStack Start build prerenders through a preview server that listens on localhost; with Node's default DNS order and an /etc/hosts that maps localhost to ::1 first (Debian bookworm does), the server binds IPv6 only and the prerender's client connects to 127.0.0.1. everyapp now runs every build it spawns (the Gateway's, cf build, cf deploy, cf dev, a manifest's build or dev command) with --dns-result-order=ipv4first appended to NODE_OPTIONS, keeping whatever was set.
  • Confirm: getent hosts localhost prints ::1; NODE_OPTIONS=--dns-result-order=ipv4first pnpm run build:release in apps/every-app-gateway succeeds.
  • Fix: update the checkout (git pull, pnpm install) and run the same command; until then, run it with NODE_OPTIONS=--dns-result-order=ipv4first set yourself. Source: packages/cli/src/lib/runtime.ts (buildNodeOptions), packages/cli/src/lib/gateway/checkoutRelease.ts, packages/cli/src/lib/cf/runner.ts, packages/cli/src/lib/app/buildApp.ts, packages/cli/src/commands/dev/appServer.ts.

Choosing or changing the Gateway's address

  • Symptom, one of: DOMAIN_REQUIRED: Choose the Gateway's address with --domain, It must be under one of this account's domains: <zones>. A subdomain works best., fix add --domain apps.<zone>, or another name under <zones> and data.zones. With one active zone it carries rerun pnpm everyapp setup --domain apps.<zone> (rerunNote or another name under <zone>); with several, data.choices[] ({ label: <zone>, rerun }, choose_account's shape) and no rerun: the agent runs one choice's rerun, or asks the person which (the install_gateway rerun status hands out is pnpm everyapp setup with no --domain, so under --json that rerun ends here). INVALID_DOMAIN: <input> isn't a full hostname, fix use a name like apps.example.com. DOMAIN_CHANGE_UNSUPPORTED: The Gateway can't move to <domain>, It's installed at <host>, and moving an installed Gateway isn't supported., rerun the same command without --domain (rerunNote (without --domain) to update it where it is). Warning GATEWAY_AT_ZONE_APEX: The Gateway's sign-in cookie will cover every subdomain of <zone>, Apps take names directly below it too. Unless the domain is only for EveryApp, install on a subdomain instead, e.g. --domain apps.<zone>, data.suggestedDomain.
  • Cause: a new install with no --domain asks at a terminal and refuses otherwise (--json, CI, a pipe). --domain is normalized (trimmed, lowercased, scheme and path stripped) and must be a full hostname. An install that finished its first upload, or one that already recorded this host, stays where it is: only an install that never uploaded can still go elsewhere. The first upload also sets the Gateway's deployment mode (GATEWAY_DEPLOYMENT_MODE, always self_hosted from the CLI) with the Gateway's generated secrets, and no later upload changes it (packages/cli/src/lib/gateway/apply.ts, packages/cli/src/lib/gateway/secretsFile.ts).
  • Confirm: pnpm everyapp status --json data.installed, data.versionId, data.mode; cf zones list --account-id <id> for the active zones.
  • Fix: unattended, pass --domain apps.<zone> (the error's rerun, or one of data.choices[].rerun); to move a live Gateway, there is no command: run without the flag to update it where it is, or install a second Gateway elsewhere. Source: packages/cli/src/commands/setup/runSetup.ts (pickDomain), packages/cli/src/commands/setup/deployGateway.ts (warnIfZoneApex), packages/cli/src/lib/domains/hostnames.ts.

Every command refuses to start

  • Symptom: UNSAFE_CF_DIRECTORY before any command does anything, one of: everyapp is running as root (sudo) with your HOME, It would leave ~/.everyapp in <home> to root., fix run it without sudo; <dir> is writable by other users|is owned by another user, so everyapp won't run cf below it, cf runs any cloudflare.config.ts it finds in a parent of its working directory (here <dir>)., fix make it writable only by you (chmod go-w "<dir>"), or run everyapp with a HOME that is; cf would run <path>/cloudflare.config.ts in every command everyapp starts, cf loads a cloudflare.config.ts from any parent of its working directory (here <dir>)., fix move that file into a project directory, rerun the same command.
  • Cause: everyapp runs cf from a private directory under ~/.everyapp/cf/<name> (mode 0700, owned by the user) and, since cf loads the first cloudflare.config.ts in its working directory or any parent, walks every ancestor of that directory: one another user can write to, or one holding a cloudflare.config.ts, is refused. Running under sudo with the user's HOME would leave ~/.everyapp to root.
  • Confirm: id -u (0 under sudo) and echo $SUDO_UID; ls -ld ~ ~/.everyapp ~/.everyapp/cf; ls ~/cloudflare.config.ts /cloudflare.config.ts.
  • Fix: as the message says: no sudo; chmod go-w on the directory named; move the stray cloudflare.config.ts into a project directory. Source: packages/cli/src/lib/cf/privateDir.ts.

Domains and TLS

Address already points somewhere

  • Symptom: DNS_RECORD_CONFLICT: <host> already points somewhere else, Its DNS record: <type value>. Publishing it replaces the record. (or N hostnames already point somewhere else listing each), with A wildcard record stays, and keeps answering every other name. when a wildcard answers it; fix delete the record in Cloudflare (<zone> → DNS), or use another name; data.rerun is the same command with --force-dns and data.authorize: true.
  • Cause: an A, AAAA, CNAME or NS record answers the new hostname, at the name itself or as *.<parent> over a name with no records of its own. cf publishes a Worker's Custom Domains as one list and, piped, replaces such a record without asking, so the check runs before anything is created or uploaded (setup: before the install; deploy: right after registering, step app.domain or gateway.domains). Mail and TXT records are not conflicts. Nor, since 2026-10-24, is the Gateway's own Custom Domain: a run that stopped after cf published the hostname (the account's first Worker stops on register_workers_subdomain after it) leaves Cloudflare's placeholder record at the name, AAAA 100::; the check reads cf workers list and, when every-app-gateway holds the hostname, says so in a progress line (<host> is already this Gateway's Custom Domain (an earlier run published it, then stopped)) and goes on. Another Worker's Custom Domain leaves the same record and is still a conflict.
  • Confirm: the zone's DNS tab in the Cloudflare dashboard, at the name and at its wildcard. An AAAA 100:: record is a Workers Custom Domain's: cf workers list --json says which Worker holds it (references.domains).
  • Fix: delete the record or pick another name (--domain for the Gateway; the app id decides an app's host). --force-dns publishes over it (warning DNS_RECORD_REPLACED): a person must say yes first. With an everyapp older than 2026-10-24, the Gateway's own placeholder record from a stopped run hits this too: git pull, then the same command. Source: packages/cli/src/lib/domains/preflight.ts.

DNS unreadable

  • Symptom: DNS_UNREADABLE: Can't read <zone>'s DNS records with this Cloudflare login, So it can't tell whether publishing <host> would replace one., fix give the API token Zone → DNS → Read, rerun with --force-dns (authorize).
  • Cause: listing the zone's records failed with cf's auth kind (API code 10000 or 9109): a CLOUDFLARE_API_TOKEN without Zone DNS Read.
  • Confirm: echo $CLOUDFLARE_API_TOKEN is set; the token's permissions in the dashboard (My Profile → API Tokens) lack Zone → DNS → Read.
  • Fix: add the permission to the token, or let a person authorize --force-dns (then the check is skipped). Source: packages/cli/src/lib/domains/preflight.ts.

CAA

  • Symptom: a certificate never comes: the CERT_PENDING warning says If it never comes, check the zone's CAA records allow Cloudflare's certificate authorities.
  • Cause: a CAA record on the zone that excludes the authorities Cloudflare issues Custom Domain certificates with.
  • Confirm: pnpm everyapp status --json data.certificates[] keeps ready: false for the host well past ten minutes; the zone's CAA records in the dashboard.
  • Fix: amend the CAA records, then wait; status reports CERT_PENDING until the pack is active. Source: packages/cli/src/lib/domains/certWait.ts, packages/cli/src/commands/deploy/steps/waitForLive.ts.

Custom domain limit

  • Symptom: ZONE_CUSTOM_DOMAIN_LIMIT: <zone> would go over Cloudflare's 100 Custom Domains per zone, The Gateway alone needs N there, before other Workers' domains., fix delete apps you no longer use (pnpm everyapp delete <app>), or install a second Gateway on another domain; data.total, data.limit. From 95 on, warning ZONE_CUSTOM_DOMAIN_LIMIT_NEAR.
  • Cause: every app hostname is a Workers Custom Domain on the Gateway Worker, and Cloudflare allows 100 per zone; other Workers' Custom Domains in the zone count too and can't be listed.
  • Confirm: data.total (the distinct hostnames the Gateway would publish in the zone) against data.limit (100); pnpm everyapp status --json data.liveDomains and data.apps[] for what counts.
  • Fix: pnpm everyapp delete <app> for unused apps, or a second Gateway in another zone. Source: packages/cli/src/lib/domains/preflight.ts.

Certificate pending

  • Symptom: after setup or a deploy, https://<host> fails the TLS handshake. The run waited up to 10 minutes (polling every 3 s, a progress line every 15 s: Waiting for a certificate: <hosts> (usually under five minutes)...), then warned CERT_PENDING: setup's https://<host> has no certificate after 10m. It usually comes within five minutes; the gateway serves it as soon as it does. … Check later with pnpm everyapp status.; a deploy's No certificate for <host> after 10m, It usually comes within 5 minutes; <app> goes live on its first visit once it has one. Exit 0: not an error.
  • Cause: Cloudflare issues each new Workers Custom Domain an advanced certificate pack (the zone apex, the hostname and *.<hostname>), which takes a minute or two and almost always under five.
  • Confirm: status --json data.certificates[] ({ host, ready }, from the zone's certificate packs), data.tls for the Gateway host, the CERT_PENDING fix (settling).
  • Fix: wait. If it never comes, CAA. Source: packages/cli/src/lib/domains/certWait.ts, packages/cli/src/lib/domains/certificates.ts.

A new address does not load

  • Symptom: a browser can't open https://<app>.<base> right after a deploy that said it is live, or the deploy itself ended with URL and Status: Goes live on its first visit.
  • Cause, one of: the certificate is pending (above); this computer's resolver still remembers the name as missing (warning LOCAL_DNS_STALE: … but this computer's DNS resolver, or this network's, still remembers <host> as missing from before it existed. That clears by itself within <N minutes>; a browser with its own DNS, such as Chrome, works now. On macOS, sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder clears this computer's own cache, but not the network's.); the app is still binding and goes active on its first request through the Gateway (503 app_deploying with retry-after: 5 until then); or the hostname isn't one label below the Gateway's base domain (warning APP_HOST_OUTSIDE_BASE: <host> isn't one label below <base>, The Gateway only serves names directly under <base>, so <app> can't be reached.; the end of the run shows Live: Not routed).
  • Confirm: status --json: data.certificates, data.apps[] (status, bound), data.liveDomains includes the host, data.appBaseDomain.
  • Fix: wait for the certificate or the resolver; open the URL once; for a host outside the base, the app id must make <id>.<appBaseDomain>. Source: packages/cli/src/lib/domains/localDns.ts, packages/cli/src/commands/deploy/deployApp.ts, packages/perimeter/src/gateway.ts.

Deploys

Schema drift

  • Symptom: SCHEMA_DRIFT: The schema has changes no migration covers, drizzle-kit would write a new migration for them. Nothing was migrated or uploaded., rerun pnpm db:generate then run this again. Or SCHEMA_CHECK_FAILED: drizzle-kit couldn't compare the schema with the migrations, <why>. Nothing was migrated or uploaded., rerun pnpm db:generate in a terminal; it asks about renamed columns and tables. A warning SCHEMA_CHECK_SKIPPED (Can't check the schema against the migrations: drizzle-kit isn't installed or no drizzle.config.ts, with An app that writes its SQL by hand sets migrations.engine to "d1-sql".) means the check didn't run.
  • Cause: after the build and before anything changes, the deploy runs drizzle-kit generate with the app's config but out pointed at a copy of the migrations under .everyapp/schema-check-*; a new .sql file there is drift. A Prisma app isn't checked (Schema not checked against migrations (Prisma needs a shadow database; --verbose shows how)), nor a release deploy (the publisher's build checked).
  • Confirm: the app.schema step under --verbose (The schema matches the migrations. when it passes; skip with its reason when it didn't run); in the app's directory, pnpm exec drizzle-kit generate --config drizzle.config.ts writes a new .sql file under the migrations folder when there is drift (delete it, or keep it as the fix); git status shows an edited schema with no new migration.
  • Fix: in the app's directory pnpm db:generate, commit the migration, deploy again. Source: packages/cli/src/commands/deploy/steps/checkSchemaDrift.ts.

Migration failed

  • Symptom: MIGRATION_FAILED. D1: Database migration failed, cf's message, Nothing was uploaded; the version that's serving keeps running., and when a restore point exists To roll the database back to before this run: pnpm everyapp db restore (db restore --gateway for the Gateway; db restore <app> for a release deploy), data.fix: "correct the migration", data.rollbackCommand (npx cf@<pinned> d1 time-travel restore <database id> --bookmark <bookmark> --force). Postgres: drizzle-kit couldn't migrate the Postgres database, fix check POSTGRES_DATABASE_URL in .everyapp/secrets.env and the migrations; DRIZZLE_KIT_MISSING when drizzle-kit isn't installed; for a Prisma app, PRISMA_MISSING (Prisma migrations need prisma, which isn't installed, Nothing was migrated or uploaded., fix add prisma to devDependencies, then deploy again). A failure later in the run (the upload) after a migration applied appends The database stays migrated. To roll it back: ….
  • Cause: cf d1 migrations apply (or drizzle-kit migrate, prisma migrate deploy) failed. Migrations run before the upload, so the serving version is untouched. A Time Travel bookmark is recorded as the restore point only when the database already had some of the migrations (a fresh database has nothing to go back to); Postgres has no restore point (its own backups).
  • Confirm: the app.migrations / gateway.migrations step's data (applied, fresh, restoreCommand, rollbackCommand).
  • Fix: correct the migration and deploy again; or pnpm everyapp db restore (asks at a terminal, --yes otherwise, else CONFIRMATION_REQUIRED; NO_RESTORE_POINT when none was saved). Restoring loses anything written since. Source: packages/cli/src/lib/migrations/remote.ts, packages/cli/src/lib/migrations/postgres.ts, packages/cli/src/commands/db/restore.ts, packages/cli/src/lib/output/report.ts.

Policy refused

  • Symptom: APP_POLICY: The built Worker can't be deployed as an EveryApp app: then one line per problem, then Build it from everyAppWorker() in cloudflare.config.ts. Also The app's Build Output isn't a production build. The manifest's build command must run cf build (production by default). and The app's Build Output has no default Worker. Related: TIER_POLICY (<app> can't be deployed as configured, fix change everyapp.config.ts, then deploy again), INSTALL_INPUTS_MISSING (The build binds <kind> <name>, which the deploy didn't provision, fix declare it in everyapp.config.ts's resources), INSTALL_PLACEHOLDER_LEFT.
  • What the list means (packages/cli/src/lib/app/policy.ts): its name is "X", not "Y" (the Worker must be named as registered); workersDev isn't false, previewUrls isn't false, it declares domains …, it declares routes …, it declares a trigger of type "…" (apps may only declare cron schedules) (apps are reached through the Gateway only); EVERYAPP_DEV is set, which only everyapp dev may do; EVERYAPP_IDENTITY_PUBLIC_KEYS isn't built in, EVERYAPP_IDENTITY_ISSUER isn't built in, EVERYAPP_AUDIENCE isn't the Worker's name; <binding> is a <type> binding, which EveryApp apps can't use (allowed: text, json, secret, version-metadata, assets, d1, kv, r2, durable-object, workflow, worker, hyperdrive); <binding> binds Worker "…" (only the Gateway's AppGateway as EVERY_APP_GATEWAY, and the app's own helper Workers); binds another Worker's Durable Object; isn't a D1 database|KV namespace|Hyperdrive config the deploy provisioned (declare it under resources); bucket and Workflow names must be the Worker's name or <worker>--<name>; Durable Object class X is marked deleted, which deletes its data; remove it from everyapp.config.ts and deploy with --allow-delete-durable-object-data instead; would move its data to Worker / would take the data of Worker; backed by a container; the build has a Worker "X" that everyapp.config.ts doesn't declare in workers; the build has no Worker <helper>, which everyapp.config.ts declares.
  • Confirm: the check runs inside the app.build step (prepareAppBuildOutput, after the build and before app.schema), so with --verbose the run stops there; the lines under the headline are the whole list, one problem each. In the app's directory, cat cloudflare.config.ts shows whether the Worker comes from everyAppWorker(), and the Build Output under .cloudflare/output/v0/ holds the worker.config.json the list was read from.
  • Fix: build from everyAppWorker(); declare every resource in everyapp.config.ts; drop the binding, route or trigger named. Source: packages/cli/src/lib/app/policy.ts, packages/cli/src/lib/app/installBuildOutput.ts, packages/cli/src/commands/deploy/deployApp.ts.

Resource name taken, owned or replaced

  • Symptom: RESOURCE_NAME_TAKEN: A D1 database|KV namespace named "<name>" already exists in this Cloudflare account, No deploy of this app created it; it may belong to another Gateway, app or project., fix delete or rename it; for Hyperdrive, A Hyperdrive config named "<name>" already exists…, fix to use it, set <BINDING>_ORIGIN_URL in .everyapp/secrets.env to the URL of the database it connects to; otherwise delete or rename it; or <resource> was created by someone else while this deploy was creating it. WORKER_NAME_TAKEN: A Worker named <name> already exists in this Cloudflare account. RESOURCE_OWNED: <resource> is recorded as another app's, fix rename the binding in everyapp.config.ts. RESOURCE_REPLACED: <resource> isn't the one this app created, That one (<id>) was deleted, and this one was created by someone else since. RESOURCE_NAME_MISSING: rerun pnpm everyapp setup to upgrade the Gateway.
  • Cause: a deploy uses an existing resource only when _everyapp_app_resources in the Gateway's D1 records it as the app's; a name proves nothing (anyone with the account can create one). Names are every-<orgKey>-<appId>[--<binding>]. The warning RESOURCE_RECREATED (The database <app> used before is gone; creating a new, empty one) is the other case: the recorded one was deleted.
  • Confirm: cf d1 list --name <name> --account-id <id> for a database, the Cloudflare dashboard's KV, Hyperdrive and Workers lists for the others; pnpm everyapp status --json data.apps[] says whether this Gateway knows an app by that id; with --verbose the app.resources step shows what was looked up.
  • Fix: delete or rename the stray resource in Cloudflare and deploy again; a Hyperdrive config that does connect to the app's database is adopted when its origin URL is in the secrets file. Source: packages/cli/src/lib/app/resources.ts, packages/cli/src/lib/app/ownership.ts.

App id or hostname taken on this Gateway

  • Symptom: the registry (the Gateway's D1, packages/perimeter/src/registry/registration.ts) refuses the registration; the CLI reports the registry's own code and message (packages/cli/src/lib/gateway/registry.ts rethrows a RegistrationError as an error with that code, retryable when its status is 500 or more). HOSTNAME_TAKEN (409): The hostname "<host>" is already registered to another app on this gateway. (the same app id in another organization). WORKER_NAME_TAKEN (409, the registry's form): The worker name "<name>" is already registered to another app on this gateway. Choose a different app id. RESERVED_APP_ID (400): The app id "<id>" is reserved for the platform. (everyapp, the prefix of the Gateway's own MCP tools). INVALID_APP_ID (400): App id "<id>" can't contain "--": it separates an app's Worker name from its resources' names. Choose another id. INVALID_APP_HOSTNAME (400): the label isn't a valid DNS label of at most 63 characters. RESOURCE_NAME_CONFLICT (400): Bindings <A> and <B> would share the <kind> "<name>". Rename one of them in everyapp.config.ts. APP_SCOPE_MISMATCH (409) and APP_TIER_MISMATCH (400 or 409): the id belongs to a shared app, or to one deployed the other way (through the hosted deploy API or over a service binding): give … an id of its own. APP_NOT_REGISTERED (409): App "<id>" has no Worker or hostname yet; deploy it again. CLI-side, around the registration: APP_CHANGING (The app's registry row keeps changing while this deploy records its upload, The upload is kept.; retryable: deploy again), SHARED_APP_FORBIDDEN (Only <org> owners and admins can <deploy|delete|change the email of> shared apps). WORKER_NAME_MISMATCH is still in register() but nothing raises it: its only caller (registerFromManifest, packages/cli/src/commands/appGateway.ts) passes no expected Worker name (the removed everyapp register did), and no command has a --worker flag.
  • Cause: an app's hostname (<id>.<appBaseDomain>) and Worker name (every-<orgKey>-<id>) are unique per Gateway, across organizations; a name proves nothing about ownership, so one held by another organization's app is refused rather than taken over.
  • Confirm: pnpm everyapp status --json data.apps[] for this organization's apps; Admin → Apps for the organization; the error's own code and status.
  • Fix: another app id (id in everyapp.config.ts), or delete the app that holds the name (pnpm everyapp delete <app> from its organization). APP_CHANGING: deploy again. Source: packages/perimeter/src/registry/registration.ts, packages/cli/src/lib/gateway/registry.ts, packages/cli/src/commands/appGateway.ts.

Hyperdrive origin missing

  • Symptom: HYPERDRIVE_ORIGIN_MISSING: <BINDING> needs the database its Hyperdrive config connects to, Creating the config needs its URL, from .everyapp/secrets.env on the machine that deploys; nothing in that file reaches the Worker., fix set POSTGRES_DATABASE_URL (or <BINDING>_ORIGIN_URL) in .everyapp/secrets.env to postgres://<user>:<password>@<host>:<port>/<database> (the binding's own variable alone when the app has several configs; data.variables lists them). pnpm everyapp status shows the same line under the app's APP_NOT_UPLOADED until its config exists. DATABASE_URL_INVALID: <VAR> in .everyapp/secrets.env isn't a URL / isn't a postgres:// or mysql:// URL / needs a host, a database, a user and a password.
  • Cause: the deploy creates the config from the origin in the app's secrets file; nothing in that file reaches the Worker (the config holds the credentials in Cloudflare). .everyapp/ is gitignored.
  • Confirm: in the app's directory grep -E '^(<BINDING>_ORIGIN_URL|POSTGRES_DATABASE_URL)=' .everyapp/secrets.env (the names only; never paste the values); resources.hyperdrive in everyapp.config.ts names the binding.
  • Fix: the person writes the variable (never ask for the value in chat; Neon and Supabase: the direct URL, not the pooled one, Postgres), then deploy again. Source: packages/cli/src/lib/app/deploySecrets.ts, packages/cli/src/lib/app/resources.ts.

Postgres URL missing

  • Symptom: POSTGRES_URL_MISSING: <app> needs a Postgres URL to migrate its database, Format: postgres://<user>:<password>@<host>:<port>/<database>, fix set POSTGRES_DATABASE_URL in .everyapp/secrets.env, then deploy again.
  • Cause: migrations.dialect is postgresql and the secrets file has no POSTGRES_DATABASE_URL; checked before anything is created. A release deploy (everyapp install <url>) refuses Postgres migrations outright (RELEASE_MIGRATIONS_UNSUPPORTED).
  • Confirm: grep -c '^POSTGRES_DATABASE_URL=' .everyapp/secrets.env in the app's directory is 0; migrations.dialect in everyapp.config.ts.
  • Fix: write it, deploy again. Source: packages/cli/src/commands/deploy/deployApp.ts.

Durable Object removed or moved

  • Symptom: DURABLE_OBJECT_REMOVED: Deploying would delete <Class>'s data for good, everyapp.config.ts no longer declares the Durable Object class <Class>.; rerun with --allow-delete-durable-object-data, data.authorize: true. DURABLE_OBJECT_MOVED: <Class> can't move to another Worker, A Durable Object's data can't follow it., fix give the class a new name in everyapp.config.ts. HELPER_DURABLE_OBJECT_REMOVED: The helper Worker <w> still defines <Class>, fix keep declaring <Class> in everyapp.config.ts.
  • Cause: the previous manifest on the registry row declared a class this one drops (deleting it deletes its data) or moves to another Worker (Cloudflare moves data only by a transfer, which the deploy never makes).
  • Confirm: git diff -- everyapp.config.ts (or git log -p -- everyapp.config.ts) in the app's directory shows the durableObjects entry removed, renamed or given another worker since the last deploy.
  • Fix: put the class back, or let a person authorize --allow-delete-durable-object-data. Source: packages/cli/src/commands/deploy/deployApp.ts (checkDurableObjects).

Workflow removed

  • Symptom: WORKFLOW_REMOVED: Deploying would delete the Workflow <binding> and its instances, everyapp.config.ts no longer declares it. Renaming a binding renames its Workflow.; rerun with --allow-delete-workflows, data.authorize: true.
  • Cause: a Workflow the last deploy defined (named <worker>--<binding>) is gone from the manifest; deleting it deletes its instances. A renamed binding counts as a new Workflow and a removed one.
  • Confirm: git diff -- everyapp.config.ts in the app's directory shows the resources.workflows binding removed or renamed; cf workflows list (if the app is deployed) for what runs now.
  • Fix: keep the binding name, or a person authorizes the flag; the Workflow is deleted once the upload is live. Source: packages/cli/src/commands/deploy/deployApp.ts (checkWorkflows), packages/cli/src/lib/app/workflows.ts.
  • Workflow names, before anything is created: WORKFLOW_NAME_TAKEN: A Workflow named "<name>" already exists in this Cloudflare account, It runs on Worker <x>, not <worker>; deploying would take it over., fix delete or rename it, rerun the same command (assertOwnWorkflows, packages/cli/src/lib/app/workflows.ts). WORKFLOW_NAME_TOO_LONG: <binding> would be the Workflow "<name>", longer than Cloudflare's 64 characters, fix shorten its binding name in everyapp.config.ts (packages/cli/src/lib/app/installBuildOutput.ts; the name is <worker>--<binding>, so a long app id leaves fewer characters for the binding).

App not live after deploy

  • Symptom: APP_NOT_LIVE: <app> isn't live after 10m: the Gateway can't reach its Worker yet, <app> stays uploaded and bound.; retryable, rerun pnpm everyapp status then deploy again. Before that, progress lines Waiting for <app> to go live (the hostname answered) or Waiting for the certificate for <host> (usually under 5m). The wait can also end without an error: warning ACCESS_IN_FRONT (behind Cloudflare Access after 10 minutes: Cloudflare Access answers before the Gateway, and the Gateway hasn't made <app> live after 10m 00s, The deploy stalled: …, data.rerun pnpm everyapp status), warning CERT_PENDING, warning APP_DISABLED (<app> is switched off in Admin → Apps), or APP_NOT_FOUND (<app> was deleted during this run). Behind Access the wait's progress line is Cloudflare Access answers before the Gateway; waiting for the Gateway to make <app> live (it looks every minute).
  • Cause: the app's registry row says binding until the Gateway marks it active, which it does on the first request that reaches the app over the service binding on a Gateway version that has it (probeApp fetches https://<host>/__everyapp/health over the binding). The wait sends GET https://<host>/ every 3 s and reads D1 after each; once Cloudflare Access answers that request it sends no more and only reads D1. The Gateway's per-minute cron probes every binding app of the install the same way (activateDeployingApps), as do the Gateway's home page and Admin → Apps for each binding app they list (activateBindingApps; the launcher reads its list again every 5 s while one is deploying), so an app is live within a minute of its upload with or without a visit; a card still Deploying after 10 minutes reads Deploy stalled and says to run status, then deploy again, which is what the CLI's ACCESS_IN_FRONT says at the same point. The Gateway answered but never activated the app: its GATEWAY_DEPLOYMENT_MODE is unset or invalid (bindingActivation off: the gateway fails closed and leaves apps deploying), the serving Gateway version isn't the one with the binding (GATEWAY_CHANGED, BINDINGS_DIFFER), or the Worker is unreachable.
  • Confirm: status --json: the app's status and bound; data.mode; data.versionId against data.recordedVersionId; data.inSync; the fixes.
  • Fix: run what status says (pnpm everyapp setup rebinds and repairs), then deploy again or open the URL once. Source: packages/cli/src/commands/deploy/steps/waitForLive.ts, apps/every-app-gateway/src/server/app-activation.ts, apps/every-app-gateway/src/server/platform-policy.ts.

Gateway outdated or upgrading during a deploy

  • Symptom: a deploy prints a heading Gateway <from> → <to> with the Gateway's database step (Database migrated, Database created or Database up to date) and Uploaded under it (step gateway.upgrade), and takes minutes longer; or status says GATEWAY_OUTDATED (Your every-app folder has a newer Gateway than the one online, Run update now, or leave it: the next new app's deploy updates it too.; run pnpm everyapp update, note updates it now; severity info, listed after the problems to fix). Related refusals: CLI_TOO_OLD (Gateway <v> needs everyapp <minCli> or newer (this is <v>), fix update everyapp (in a checkout: git pull), then run this again), RELEASE_UNSUPPORTED (Gateway release <v> needs a newer everyapp), and DOWNGRADE_REFUSED when the checkout is the older one.
  • Cause: a deploy that adds an app or a hostname rebinds the Gateway with the Gateway this checkout builds; when that differs from the live release it upgrades it on the way (migrations first, under the gateway lease). A code-only redeploy of an app the Gateway already binds never touches the Gateway. upToDate compares the checkout's tree hash with the install row's.
  • Confirm: status --json data.upToDate, data.checkout, data.release.
  • Fix: nothing, when the upgrade went through; pnpm everyapp setup upgrades on its own; a failed upgrade after its migration says how to db restore --gateway. Source: packages/cli/src/commands/appGateway.ts (bindIfNeeded), packages/cli/src/lib/gateway/reconcile.ts, packages/cli/src/lib/gateway/apply.ts, packages/cli/src/lib/gateway/release.ts.

Release not found, no release, too large or corrupt

  • Symptom: RELEASE_NOT_FOUND: <owner/repo> has no release <tag>|published release with everyapp-release.tar.gz that can be downloaded without signing in (https://github.com/<owner/repo>/releases)., <url> doesn't exist (404)., <x> isn't a GitHub repository (https://github.com/<owner>/<repo>)., or for a path <source> is neither a URL nor a file. NO_RELEASE: <owner/repo> publishes no everyapp release, Its GitHub releases have no everyapp-release.tar.gz., fix clone it, then run pnpm install && pnpm everyapp deploy in it. RELEASE_TOO_LARGE: <url> is over 200 MB, too big for a release. RELEASE_CORRUPT: Gateway|App release <v> doesn't match its checksum, The download may be damaged. Download it again. (data.expected, data.actual). RELEASE_INVALID: not that kind of release, extra top-level entries, a malformed release.json, a manifest this everyapp refuses, an app id that isn't the manifest's, migrations outside migrations/. RELEASE_DOWNLOAD_FAILED: an HTTP status, too many redirects, a redirect off HTTPS, or a 5-minute timeout (retryable for 5xx and timeouts).
  • Cause: downloads are anonymous (no token, cookie or header), so a private repository's releases answer 404; releases/latest/download/<asset> redirects to the tag, which names the release. An app's asset is everyapp-release.tar.gz; the Gateway is never downloaded (it is built from the checkout: Not in the checkout). The SHA-256 is over every file but release.json.
  • Confirm: curl -sI https://github.com/<owner>/<repo>/releases/latest/download/everyapp-release.tar.gz answers 302 (to the tag's asset) when a public release has the asset and 404 otherwise (a private repository answers 404 too); data.expected against data.actual for a corrupt one.
  • Fix: publish the asset, pass /releases/tag/<tag>, or clone and deploy from the source; re-download a corrupt one. Source: packages/cli/src/lib/release/github.ts, packages/cli/src/lib/release/fetch.ts, packages/cli/src/lib/app/release.ts, packages/cli/src/lib/gateway/release.ts.
  • Packing a release fails (everyapp release): RELEASE_LOCAL_STATE: Refusing to pack local state or secrets into the release: then the paths (anything named .dev.vars*, .env*, *.pem, under .wrangler/, .everyapp/, .cloudflare/state or .cloudflare/types): move them out of what the build emits. RELEASE_PACK_FAILED: The app's Build Output isn't a production build, A release ships only one., fix build with cf build (production by default); Expected the Build Output v0 alone in <dir>/.cloudflare/output, found <entries|none>; or tar couldn't pack the release (exit <N>): <stderr>.

The Gateway's deploy API and browser installs

The Gateway has a deploy API of its own (apps/every-app-gateway/src/server/deploy/), which no everyapp command calls; its refusals reach a person in the browser. Two ways in share its pipeline (run.ts, pipeline.ts): a browser install (Admin → Apps → Install app: an owner or admin gives a public GitHub repository URL or an oss.new release URL, the Gateway downloads the app's everyapp-release.tar.gz and deploys it, registered as a third-party app of that organization; install.ts: GET /api/admin/apps/install, GET /api/admin/apps/install/context, POST /api/admin/apps/install, GET /api/admin/deploys/{id}, which the page polls), and the deploy-key routes under /api/v1/ (routes.ts, auth.ts), which exist only on a hosted Gateway while its launch gate THIRD_PARTY_DEPLOYS is on (otherwise their paths are the control plane's 404s). A refusal is JSON { "error": "<code>", "message": "<sentence>" } with the HTTP status below. A deploy runs in the request that starts it and the 30 s of waitUntil after it: the answer is the job, finished when the deploy took under 20 seconds and else running, to poll; a job that failed keeps the code and message as its error (DEPLOY_FAILED, The deploy failed in the gateway. Deploy again., when the failure had no code). Error codes has an entry per code.

  • Browser installs are off: APP_INSTALL_NOT_AVAILABLE (404): Browser installation is not enabled on this Gateway. Its owner can configure the browser deployer, or deploy with the everyapp CLI.; GET /api/admin/apps/install answers { "available": false }. A self-hosted Gateway installs only with BROWSER_APP_INSTALLS exactly on (off by default) beside CF_DEPLOY_TOKEN and CLOUDFLARE_ACCOUNT_ID, for the install organization's owners and admins from a session under 10 minutes old (APP_INSTALL_REAUTH_REQUIRED otherwise); it used to install when CF_DEPLOY_TOKEN and CLOUDFLARE_ACCOUNT_ID were set on the Gateway Worker, APP_BASE_DOMAIN is (setup writes it) and BROWSER_APP_INSTALLS isn't off (selfHostedInstallEnabled, browser-install.ts); a hosted one while its launch gate is on. Setup asks for the token once, at a terminal, when the Worker has no CF_DEPLOY_TOKEN (--yes answers No; a token set stays: packages/cli/src/commands/setup/runSetup.ts); later, pnpm everyapp secret set CF_DEPLOY_TOKEN; pnpm everyapp secret set BROWSER_APP_INSTALLS with off turns installs off. On a self-hosted Gateway an installed app gets BROWSER_INSTALL_LIMITS (5,000 ms of CPU, 200 subrequests, 2 Durable Object classes; browser-install.ts): D1, its own Durable Objects and static assets.
  • Who may install, and the request: UNAUTHORIZED (401: Sign in again., Organization membership required.), APP_INSTALL_FORBIDDEN (403: Only the organization's owners and admins install apps.), ORGANIZATION_SUSPENDED (403: The organization is suspended.), INVALID_REQUEST (400: Send JSON with one repoUrl or releaseUrl., Send one repoUrl or releaseUrl., Choose a valid workspace.), INVALID_REPO_URL (400: Paste a GitHub repository URL: https://github.com/<owner>/<repo>.), INVALID_RELEASE_URL (400: Use an oss.new release URL: https://oss.new/apps/<key>.).
  • The release the Gateway downloads (release.ts; nothing is downloaded for a Gateway that couldn't deploy it): NO_RELEASE (404): <owner>/<repo> publishes no everyapp release (its latest GitHub release has no everyapp-release.tar.gz). Clone it and run `pnpm everyapp deploy` with a deploy key (Admin → Tokens → Deploy key). (a repository without a release is deployed by cloning it and running pnpm install && pnpm everyapp deploy in it, with your Cloudflare login; deploy keys are the /api/v1/ routes' credential, which no everyapp command sends). RELEASE_FETCH_FAILED (502), RELEASE_TOO_LARGE (413: The release is larger than 100 MB …; the gateway installs smaller ones only.), RELEASE_INVALID (422), RELEASE_UNSUPPORTED (422: The release's schema is <x>; this gateway reads schema 1.), RELEASE_CORRUPT (422: Release <v> doesn't match its SHA-256 (expected <x>, got <y>).). The asset is what pnpm everyapp release packs (above).
  • BUNDLE_REFUSED (HTTP 422, a route or the job): The app can't be deployed as a third-party app: then one reason per line. Fix: change everyapp.config.ts as the reasons say, publish a new release, install or deploy again. The dispatch tier's rules (manifestTierProblems, packages/perimeter/src/manifest/manifest.ts): resources.hyperdrive: Hyperdrive isn't available to apps in a dispatch namespace (an account holds at most 25 configs), resources.r2: R2 buckets aren't available to apps in a dispatch namespace yet, no inbound mail (the platform's zones aren't an organization's to route), secrets: third-party apps can't have secrets of their own yet. The deployer also refuses helper Workers: it declares helper Workers (workers), which third-party apps can't have and a Durable Object class names a helper Worker from the manifest check (apps/every-app-gateway/src/server/deploy/metadata.ts), and for a release's Build Output its Build Output has helper Workers (<names>), which third-party apps can't have (apps/every-app-gateway/src/server/deploy/release.ts).
  • The API's other codes, with the HTTP status and what to do:
Code Status Headline Fix
PROTOCOL_TOO_OLD 426 This CLI packages deploy protocol <N>; the gateway reads <M> or later. Update everyapp and deploy again. (an /api/v1/ upload older than MIN_HOSTED_DEPLOY_PROTOCOL, packages/perimeter/src/manifest/deploy.ts) an upload with the current protocol
DEPLOY_IN_PROGRESS 409 Another deploy of <app> is running; wait for it, then deploy again. (a browser install: Another install or CLI operation is running. Try again when it finishes.) wait, then again
DEPLOY_ABANDONED the job's error The deploy stopped without finishing: the gateway gives a deploy about 50 seconds, and a large app (many assets, slow migrations) can run out of them. Deploy again. (a job still running after 5 min; apps/every-app-gateway/src/server/repositories/DeployJobRepository.ts, docs/runbooks/hosted-install.md) again
BUNDLE_TOO_LARGE 413 The upload is <N> bytes; the deploy API takes at most 67108864. or The upload holds more than 10000 files or 67108864 bytes. (/api/v1/ uploads; bundle.ts) fewer or smaller assets
INVALID_BUNDLE 400 a malformed /api/v1/ upload (a stamp with no protocol version, a part that isn't a file, …) a well-formed upload
DEPLOYER_NOT_CONFIGURED 503 one of Set CF_DEPLOY_TOKEN and CLOUDFLARE_ACCOUNT_ID on the Gateway., Set APP_BASE_DOMAIN before installing apps., The Gateway's CLI install record is missing. Finish Gateway setup before installing apps., This account has no Custom Domain for the Gateway., App hosts must be below this Gateway's hostname., A self-hosted app needs its Worker and hostname., This gateway has no GATEWAY_URL or JWT_PUBLIC_KEY to give apps., This gateway's deployer isn't configured (CF_DEPLOY_TOKEN, CLOUDFLARE_ACCOUNT_ID and DISPATCH_NAMESPACE)., THIRD_PARTY_DOMAIN (<x>) overlaps the gateway's own domain (<y>); third-party apps need a registrable domain of their own.; 502 for Cloudflare's answers: Cloudflare did not return one serving version for <worker>. Reconcile it with the CLI., Cloudflare did not return the Gateway's bindings. Nothing was replaced. the setting named, by whoever owns it: pnpm everyapp secret set <NAME> for CF_DEPLOY_TOKEN, DISPATCH_NAMESPACE or THIRD_PARTY_DOMAIN; pnpm everyapp setup for the install record, APP_BASE_DOMAIN, CLOUDFLARE_ACCOUNT_ID and GATEWAY_URL (setup writes them once; secret set answers SECRET_NOT_SETTABLE); JWT_PUBLIC_KEY as in Gateway keys rotated or lost; the hosted settings per docs/runbooks/hosted-install.md; for a 502, pnpm everyapp setup
MIGRATION_FAILED 422 Migration <name> failed: <why>. Nothing was uploaded; the version that is serving keeps running. correct the migration, publish a new release, install again
BINDINGS_MISMATCH 502 Cloudflare lists other bindings for <worker> than the deployer sent (<names>), so the Worker was deleted. Deploy again; if it happens again, the gateway's operators need to look.; Cloudflare did not return the Worker's bindings.; Cloudflare did not retain the app's Gateway binding. Try again. again; then the operator
APP_NOT_REGISTERED 409 The app has no Worker name. or App "<id>" has no Worker or hostname yet; deploy it again. again
HOSTNAME_TAKEN, WORKER_NAME_TAKEN 409 … is already registered to another app on this gateway.; from the self-hosted deployer, Another Worker owns the app's hostname. or DNS already answers for this app's hostname. Choose another app id. another app id (App id or hostname taken on this Gateway)
RESERVED_APP_ID, INVALID_APP_ID, RESOURCE_NAME_CONFLICT 400 as in that entry another app id, or rename the binding
APP_TIER_MISMATCH 400 or 409 A shared app is reached over a service binding; "<id>" can't be deployed through the hosted deploy API. / App "<id>" is reached over a service binding, not deployed through the hosted deploy API; give the third-party app an id of its own. another app id
APP_ALREADY_INSTALLED 409 This app is already installed. Open it from your Gateway; use the CLI to update it. pnpm everyapp install <release url>, or pnpm everyapp deploy from the app's directory
WORKER_OWNED, WORKER_CHANGED, BINDING_OWNED 409 A Worker with this name already exists outside this browser installer. Use the CLI to manage it. or Cloudflare did not list the existing Worker's ownership. Nothing was uploaded.; This Worker changed outside the browser installer. Use the CLI to reconcile it.; The Gateway already uses this app binding for something else. pnpm everyapp deploy from the app's directory, or another app id
LEASE_LOST, APP_CHANGING 409 The install lost its deployment lease. Try again.; This app changed before installation started. Try again., The app's registry row changed while the deploy recorded its upload. Deploy again. again
APP_NOT_FOUND, DEPLOY_NOT_FOUND 404 The app's registry row is gone; register it again.; No such deploy. install again
ORG_KEY_UNAVAILABLE 409 Every key for organization "<slug>" is taken by another organization on this gateway. as it says
INVALID_MANIFEST 400 the manifest's own error, or manifest.id "<id>" does not match the app id "<id>". fix the release's manifest
  • Deploy keys (the /api/v1/ routes; HTTP 401 or 403): DEPLOY_KEY_REQUIRED (Send a deploy key as the bearer token (EVERYAPP_DEPLOY_TOKEN).; no everyapp command reads that variable), DEPLOY_KEY_INVALID (The deploy key is unknown, revoked or expired. Create one in Admin → Tokens (Deploy key).), DEPLOY_SCOPE_REQUIRED (This token isn't a deploy key. Create one in Admin → Tokens (Deploy key).: a personal token with other scopes), NOT_A_MEMBER (The deploy key's owner is no longer a member of its organization, or is deactivated.), DEPLOY_FORBIDDEN (Only the organization's owners and admins deploy apps.), ORGANIZATION_SUSPENDED, DEPLOY_KEY_APP_MISMATCH (This deploy key is bound to another app.: the key was created for one app); NOT_FOUND (404, No such deploy API route.) for a path the API doesn't serve, and a plain 404 for every /api/v1/ path while the gate is off. A deploy key is a personal access token with the deploy scope and nothing else (* doesn't count), made in Admin → Tokens (kind Deploy key, 30 days by default, optionally bound to one app); on an app host it opens nothing (Personal access token rejected).
  • Source: apps/every-app-gateway/src/server/deploy/install.ts, apps/every-app-gateway/src/server/deploy/browser-install.ts, apps/every-app-gateway/src/server/deploy/run.ts, apps/every-app-gateway/src/server/deploy/routes.ts, apps/every-app-gateway/src/server/deploy/auth.ts, apps/every-app-gateway/src/server/deploy/errors.ts, apps/every-app-gateway/src/server/deploy/bundle.ts, apps/every-app-gateway/src/server/deploy/release.ts, apps/every-app-gateway/src/server/deploy/cfRestDeployer.ts, apps/every-app-gateway/src/server/deploy/registry.ts, apps/every-app-gateway/src/server/deploy/pipeline.ts, apps/every-app-gateway/src/server/repositories/DeployJobRepository.ts, packages/cli/src/commands/setup/runSetup.ts, docs/runbooks/hosted-install.md.

Running apps

401 or 403 from an app

Read the response body: the Gateway's answers are { "error": …, "reason"?: … }; the SDK's (the app's own Worker) are { "error": "unauthenticated", "message": … } for 401 and { "error": "misconfigured", "message": … } for 500.

  • From the Gateway, 401 unauthenticated (WWW-Authenticate: Bearer realm="every-app"): no session and not a public route; an HTML navigation gets a 302 to the Gateway's sign-in with ?return_to= instead. A bearer the Gateway consumed but can't sign in with also answers 401: an unknown, revoked or expired epat_ token, one whose owner left or was deactivated, or a deploy key (its deploy scope opens no app). 401 with reason: "duplicate_session_cookie": the Cookie header names the session cookie twice (cookie tossing); the browser must drop the extra cookie. 403 forbidden with reason: "no_app_access": a signed-in member the app isn't shared with (Admin → Apps → Manage access, or Available to everyone; user docs), a suspended organization, or a personal token bound to another app. 403 forbidden with reason: "token_scope": an MCP-only token (all mcp: scopes) on any route but /mcp, or on any route of a host whose manifest has no mcp (the default: such a host serves no MCP). 403 csrf_denied and 403 server_fn_denied: next entry. packages/perimeter/src/gateway.ts, apps/every-app-gateway/src/perimeter/betterAuthAuthenticator.ts.
  • From the SDK, 401 unauthenticated with message: request reached the app without an identity token (not proxied through the gateway?) (the request bypassed the Gateway, or a public route the deployed manifest no longer declares), unexpected kid "…" / unexpected alg "…", identity verification failed: … (the token's signature doesn't match EVERYAPP_IDENTITY_PUBLIC_KEYS: after a Gateway key rotation every app must be redeployed, docs/runbooks/key-rotation.md; aud must equal EVERYAPP_AUDIENCE, the Worker's name; iss must equal EVERYAPP_IDENTITY_ISSUER, the Gateway URL), EVERYAPP_IDENTITY_ISSUER not configured — deploy with the EveryApp CLI or provide an explicit issuer., unexpected token typ. 500 misconfigured: EVERYAPP_IDENTITY_PUBLIC_KEYS is not set — the gateway must inject the identity public key set., EVERYAPP_AUDIENCE is not set — the app's cloudflare.config.ts must declare it (everyAppWorker() does at deploy)., or Dev identities are accepted only from the dev issuer https://gateway.dev.localhost, not "…". EVERYAPP_DEV is for everyapp dev alone. (EVERYAPP_DEV reached production: the deploy policy refuses a build with it, so this means a Worker uploaded some other way). packages/sdk/src/internal/identity.ts, packages/sdk/src/server/everyApp.ts.
  • Confirm: curl -s -o /dev/null -w '%{http_code}\n' https://<host>/<path> with and without a session; the body's error and reason/message. A deploy stopping with IDENTITY_KEY_UNKNOWN (The Gateway is missing its sign-in public key) means the install row has no jwt_public_key: step 3 of the key rotation runbook.
  • Fix: grant the app; sign in; redeploy the app after a key rotation (pnpm everyapp deploy from each app's directory); build with everyAppWorker(). Identity, headers and the SDK, Key rotation.

Gateway keys rotated or lost

  • Symptom: every app answers 401 unauthenticated with identity verification failed: … after the Gateway's JWT_PRIVATE_KEY / JWT_PUBLIC_KEY changed; or a deploy stops with IDENTITY_KEY_UNKNOWN: The Gateway is missing its sign-in public key, App Workers need it to check who is signed in., data.fix restore JWT_PUBLIC_KEY as in https://github.com/every-app/every-app/blob/main/docs/runbooks/key-rotation.md.
  • Cause: an app gets the public key as EVERYAPP_IDENTITY_PUBLIC_KEYS at deploy time, read from the install row (_everyapp_install.jwt_public_key in the Gateway's D1), which the CLI wrote when it generated the pair at the Gateway's first deploy; no endpoint serves it. A Gateway signing with a new private key fails every app that still holds the old public key, on purpose (fail closed) until each is redeployed. A row with no key (a Gateway first deployed by an older CLI) is what IDENTITY_KEY_UNKNOWN reads.
  • Confirm: pnpm everyapp status lists the apps; an app's 401 body says identity verification failed; the deploy's error code.
  • Fix, the runbook's five steps (docs/runbooks/key-rotation.md, mirrored): 1. list the apps (pnpm everyapp status) and plan a maintenance window. 2. Generate a pair with node apps/every-app-gateway/regenerate-secrets.js (a PKCS#8 private key and an SPKI public key, RS256, 2048-bit; an OpenSSH or PKCS#1 BEGIN RSA PRIVATE KEY key deploys without error and fails at runtime) and replace both JWT_PRIVATE_KEY and JWT_PUBLIC_KEY on the Gateway Worker in one change (Workers & Pages → every-app-gateway → Settings → Variables and Secrets); never one half alone. 3. Record the public key in the install row: write { "sql": "UPDATE _everyapp_install SET jwt_public_key = ?1 WHERE id = 'install'", "params": ["-----BEGIN PUBLIC KEY-----\n…\n-----END PUBLIC KEY-----\n"] } to a private key.json (the PEM's line breaks as \n), then CLOUDFLARE_ACCOUNT_ID=<account id> npx cf@1.0.0-beta.14 d1 query <database id> --body @key.json (npx cf@1.0.0-beta.14 d1 list --name every-app-gateway gives the id); delete key.json after. For a lost key (IDENTITY_KEY_UNKNOWN), this step alone, with the Gateway's current JWT_PUBLIC_KEY. 4. pnpm everyapp setup --redeploy at a terminal and confirm cf's prompt (the Worker changed outside everyapp, so an unattended run would stop with rerun_interactive; --redeploy is Upload the Gateway even when it is up to date (after a change in the dashboard, such as a key rotation), a hidden flag of setup). 5. pnpm everyapp deploy from each app's directory. Then verify a private route answers 401 without a session and 200 with one, and a forged x-everyapp-identity header still gets 401. Source: docs/runbooks/key-rotation.md, packages/cli/src/commands/deploy/deployApp.ts, packages/cli/src/commands/setup/command.ts.

A public route that is not public

  • Symptom: an anonymous request to a path the person meant to be public gets 401 (or a 302 to sign-in), or a webhook's POST gets 401.
  • Cause: routes are private unless public in everyapp.config.ts lists them; methods defaults to GET only; the path must canonicalize unambiguously (an encoded separator, double encoding, traversal, a backslash or control characters make it ambiguous: never public); /__everyapp/* and the RFC 9728 metadata paths are never public; a glob * is one segment, ** crosses segments, :name one non-empty segment. The manifest the Gateway reads is the one recorded by the last deploy (markDeployed), so a manifest edit needs a deploy. A public POST that carries a cookie and fails the CSRF check isn't denied: it is served anonymously (the public marker), so a signed-in member can look logged out there. The boundary this enforces (every request reaches an app through the Gateway, and a route is private unless the deployed manifest declares it) is written up in /docs/agent/repo/threat-model (assets, actors, trust boundaries, one testable invariant per boundary; mirrored from docs/threat-model.md).
  • Confirm: curl -s -o /dev/null -w '%{http_code}\n' https://<host>/<path> expects 200 for a declared public GET; curl -s -o /dev/null -w '%{http_code}\n' -X POST https://<host>/__everyapp/tools/call is 404 by design (docs/TESTING.md, section C).
  • Fix: add the route (path and methods) and deploy again; in the app, a public request arrives with user null and x-everyapp-public set. Source: packages/perimeter/src/publicRoutes.ts, packages/perimeter/src/gateway.ts.

CSRF or server function guard blocking a request

  • Symptom: 403 { "error": "csrf_denied", "reason": … } or 403 { "error": "server_fn_denied", "reason": … } from a page's own request; a WebSocket upgrade refused.
  • Cause: a state-changing request (not GET, HEAD, OPTIONS) that carries the session cookie must have Sec-Fetch-Site: same-origin, or without that header an Origin equal to the app's host; reasons: sec-fetch-site cross-site|same-site|none, no origin and no sec-fetch-site, malformed origin, origin <x> != <host>. A sibling app's page is same-site, not same-origin, so it is refused too. Requests with a bearer token or without a cookie skip this (a cookieless POST gets 401, not 403). A cookie-authenticated WebSocket upgrade must send Origin equal to the app's host (websocket upgrade without origin, websocket origin <x> != <host>). TanStack Start server functions under /_serverFn (or the manifest's serverFnBase) answer only same-origin fetch() calls carrying x-tsr-serverFn: true or x-tsr-redirect: manual: reasons server function navigation, sec-fetch-site <x>, missing server function client header. serverFnGuard: false in the manifest turns that guard off.
  • Confirm: the reason in the body; the request's Origin, Sec-Fetch-Site, Sec-Fetch-Mode and x-tsr-* headers in the browser's network panel.
  • Fix: call from the app's own origin with fetch(); for cross-site callers (webhooks) declare the route public and authenticate it in the app; set serverFnBase when server functions live under a router basepath. Source: packages/perimeter/src/csrf.ts, packages/perimeter/src/serverFunctions.ts, packages/perimeter/src/gateway.ts.

AI or capability call refused

  • Symptom: a call through the EVERY_APP_GATEWAY binding (createGatewayFetch(), integrationFetch(), sendEmail(), listMembers()) fails; the response carries x-everyapp-error: <code> and a body { error, code, message } (AI calls: OpenAI's error shape with type: "gateway_refusal", plus code and message at the top level), which the SDK's gatewayRefusal() / GatewayRefusalError reads.
  • Codes: ai_provider_not_configured (503, Provider is not configured: the gateway needs <SETTING> [and <SETTING>] for <provider>.), ai_provider_not_supported (404), ai_route_not_allowed (403), model_not_allowed (403, list it in the manifest's ai.models), ai_request_invalid (400), ai_upstream_failed; capability_not_requested (403: the deployed manifest doesn't list the capability), invalid_integration_route (400), integration_path_not_allowed (403), integration_method_not_allowed (405), integration_not_configured (503, the message names the Gateway setting to set), integration_upstream_failed (502), integration_agent_not_allowed (403: An agent's tool call can't list the organization's members., or An agent can't connect or refresh <label> accounts: the person does it in the app.; the request was made for an MCP tool call), integration_body_too_large (413, an OAuth token request's body over the limit), integration_body_invalid (400); for an OAuth client with scope sets (google-workspace, microsoft-365, slack): integration_scopes_undeclared (403: the manifest entry has no valid scopes list, so the whole grant is void), integration_grant_not_allowed (403: a grant_type that isn't a user's grant), integration_scope_not_allowed (403: the token request asks a scope outside the app's sets), integration_scope_exceeded (403: the provider granted scopes outside them; nothing is forwarded or stored), integration_scope_unverified (502: the token names no scopes), integration_response_invalid (502). Build the request from integrationStatus()'s scope sets or authorizeUrl(); to use more, add the set to the entry's scopes in everyapp.config.ts and deploy (Identity). The entrypoint's own refusals about who is calling (app-gateway-entrypoint.ts): caller_not_registered (403, No registered app matches the service-binding caller identity.), caller_not_active (403, The calling app is not active.: an app still pending or binding, or disabled, calling AI, email or members; see App states, check status --json data.apps[].status and open the app's URL once; also Shared apps are off on this gateway.), caller_identity_mismatch (403, Service-binding caller identity does not match the registry.), caller_identity_invalid (401, The forwarded identity is invalid or expired.), caller_identity_required (403, a shared app's provider call without the request's identity or the organization header for background work), caller_org_mismatch (403, the organization header doesn't match whom the call is for), caller_org_not_attributable (403, The organization this call is for is unknown, suspended or has no active members.), caller_registry_unavailable (500, the registry or organization lookup failed), invalid_gateway_route. The Gateway's entrypoint caches who a caller is for 30 s, so a just-changed app row takes up to 30 s to count.
  • Confirm: pnpm everyapp secret list <app> (from anywhere; in the app's directory the id can be left out) shows each Gateway setting the manifest's providers and capabilities need with ✓ set, ! not set (required, or every member of an any-of group unset), not set (optional) or not set (one of: <group>), and under --json data.app.setup (ready, unmet, command); from the app, integrationStatus() answers { capability, requested, configured, ready }.
  • Fix: a Gateway setting is set once for every app, from any directory: pnpm everyapp secret set <SETTING> (OPENAI_API_KEY, OPENROUTER_API_KEY, WORKERS_AI_API_TOKEN with CLOUDFLARE_ACCOUNT_ID, …; live at once, no deploy), or pnpm everyapp configure <app> for everything the app needs; a missing capability goes in everyapp.config.ts and needs a deploy. In everyapp dev there is no Gateway: the SDK falls back to provider keys in .dev.vars. Step by step per setting: packages/cli/skills/everyapp/setup/openai.md, setup/openrouter.md, setup/workers-ai.md, setup/dataforseo.md, setup/context.md, setup/google.md, setup/microsoft-365.md, setup/slack.md (index setup/README.md; in an app, .claude/skills/everyapp/setup/<name>.md). Secrets, Gateway settings and capabilities, Capabilities. Source: apps/every-app-gateway/src/server/ai-proxy.ts, apps/every-app-gateway/src/server/capabilities/refusal.ts, apps/every-app-gateway/src/server/app-gateway-entrypoint.ts, docs/capabilities.md.

Email not sent or not received

  • Sending, by refusal code (apps/every-app-gateway/src/server/capabilities/email.ts): capability_not_requested (403): add capabilities: { email: { access: "write" } } and deploy. integration_not_configured (503): Email isn't set up on this gateway. Its operator sets up a sending domain, then sets EMAIL_FROM and EMAIL_FROM_NAME and <a transport>, with everyapp secret set <NAME> (user docs, Email sending setup; step by step for an agent: packages/cli/skills/everyapp/setup/email.md, in an app .claude/skills/everyapp/setup/email.md). email_sender_not_allowed (403): the message's from isn't among the installation's senders (pnpm everyapp email set --sender '*@<domain>' in the app's directory, after its first deploy), or the Gateway is hosted (own sender only; the CLI refuses EMAIL_SENDERS_HOSTED). email_sender_not_verified (422): the sender's domain isn't onboarded with the provider. email_provider_limited (429), email_budget_exhausted (429 with retry-after: 100 mails a day per recipient and 5,000 per self-hosted Gateway), email_budget_unavailable (503), email_message_too_large (413), email_invalid_message (400), integration_read_only (403: a read-only agent connection). The SDK's own email_send_failed (The EveryApp gateway answered <status> to the send.) is raised by sendEmail() when the response is neither a refusal nor ok (packages/sdk/src/server/sendEmail.ts). Every send logs email.app.send (never the subject or body). The CLI's own refusals: EMAIL_NOT_DECLARED (<app> can't send mail yet: everyapp.config.ts doesn't list email in its capabilities.); the arguments of email set, before any manifest or network: EMAIL_NOTHING_TO_SET (Name what to set: --sender, --inbound, or both), EMAIL_SENDER_INVALID and EMAIL_INBOUND_INVALID (<N> problems with --sender|--inbound with one • <problem> line each and data.problems, or the single problem as the headline).
  • Receiving: the manifest needs email: { inbound: true } and the Worker an email() export; the addresses are the installation's (pnpm everyapp email set --inbound <address>; *@<apex> is the zone's catch-all). pnpm everyapp email show prints each address as routed, not routed yet (deploy routes it), not declared (the manifest doesn't declare inbound: pnpm everyapp email unset inbound clears it) or removed, still routed. status reports EMAIL_INBOUND_UNDECLARED, EMAIL_ROUTE_PENDING and EMAIL_ROUTE_MISSING (the route's problem in words: its Email Routing rule is gone, its rule sends mail <elsewhere> / is off, the catch-all …, Email Routing is off for <domain>, a deploy stopped before recording its rule): pnpm everyapp deploy from the app's directory remakes the routes. Refusals before anything is written: EMAIL_ROUTING_DISABLED (Email Routing is off for <domains>, Turning it on locks the domain's MX and SPF records to Cloudflare. Nothing was changed.; unattended only, rerun with --enable-email-routing, data.authorize: true, data.commands the npx cf email-routing enable … lines), EMAIL_ADDRESS_TAKEN (<address> already goes to another app (<app>) or already <delivers …> by a rule everyapp didn't make), EMAIL_CATCH_ALL_NOT_APEX, EMAIL_CATCH_ALL_TAKEN, EMAIL_ZONE_NOT_FOUND (<domain> isn't on Cloudflare DNS in this account), EMAIL_INBOUND_NOT_DECLARED. A stop after the row was written: EMAIL_ROUTES_PARTIAL (Inbound addresses saved, but routing them stopped part-way; retryable: the same command again, or pnpm everyapp deploy).
  • The Gateway reads a sender change within 30 s (it caches the app's row that long). Email. Source: packages/cli/src/commands/email/index.ts, packages/cli/src/lib/app/emailRoutes.ts, packages/cli/src/commands/status/problems.ts, docs/runbooks/email-sending-setup.md.

App states

What the launcher and Admin → Apps show for an app, from its registry row (apps/every-app-gateway/src/client/lib/appStatus.ts; the statuses are pending, binding, active, disabled in packages/perimeter/src/registry/apps.ts):

  • Needs deploy: no hostname, or status pending: registered, but no deploy of it finished (its Worker isn't uploaded). status says APP_NOT_UPLOADED. Fix: pnpm everyapp deploy in the app's directory (or pnpm everyapp delete <app>). Requests to its host answer 503 { "error": "app_deploying", "status": "pending" }.
  • Deploying: status binding: the Worker is uploaded and the row waits for a Gateway version that binds it to serve a request; that request makes it active (the deploy's live wait sends it). Requests answer 503 app_deploying with retry-after: 5. status says APP_BINDING when the live Gateway binds it (it goes live on its first visit: open the URL) and BINDINGS_DIFFER when it doesn't (pnpm everyapp setup). It stays Deploying for good when the Gateway's GATEWAY_DEPLOYMENT_MODE is unset or invalid (activation is off: App not live after deploy).
  • Disabled: status disabled: an admin switched it off in Admin → Apps. Requests answer 503 { "error": "app_unavailable", "status": "disabled" }; a deploy warns APP_DISABLED and leaves it uploaded. Fix: turn it on there.
  • Needs setup: status active, but a required setting of its manifest (a provider's key, a capability's client, one of its own secrets; required unless marked optional: true or anyOf) isn't set. A private HTML GET navigation gets a 302 to https://<gateway>/?setup=<app> (the launcher's setup dialog); everything else, public routes included, gets 503 { "error": "app_needs_setup", "app": "<app>" } with no retry-after and never a setting's name; its tools stay out of /mcp. status says APP_NEEDS_SETUP (<app> needs setup) with the one command, pnpm everyapp configure <app>, and the deploy that uploaded it warned APP_NEEDS_SETUP (<app> won't open until <NAMES> is set) with the same command in next. Confirm: pnpm everyapp secret list <app> (data.app.setup.unmet, data.app.setup.command); pnpm everyapp configure <app> shows the same table before it asks. Fix: run the command it prints, pnpm everyapp configure <app> (at a terminal it asks for each unmet setting, with where to get it; a script pipes NAME=value lines in or adds --from-env; one name: pnpm everyapp secret set <NAME> [--app <app>]); live at once, no deploy; it ends with <app> is ready. Source: apps/every-app-gateway/src/server/app-needs-setup.ts, apps/every-app-gateway/src/server/app-setup.ts, docs/manifest-settings.md.
  • An MCP chip beside the name: an MCP app (tier provider, no Worker, nothing to open): the built-in DataForSEO (pnpm everyapp mcp on dataforseo) or a remote MCP server added by URL (pnpm everyapp mcp add <url>). Listed under "MCP apps" on the launcher, its card opens the "Use it from your agent" dialog (the /mcp address, <id>_<tool>) rather than a page. Active once its key or credential is set; Needs setup while it isn't (pnpm everyapp secret set DATAFORSEO_API_KEY, or the app's <ID>_MCP_TOKEN), or while a remote app's credential is pinned to another origin (pnpm everyapp mcp show <id> says which; secret set again pins it here); Off after mcp off (not served, everyone's access kept; mcp on serves it again); Not supported yet for a server that wants OAuth. mcp remove leaves a tombstone, so the app is gone from the launcher and Admin → Apps (before 2026-10-25 mcp off did that). A remote app with no tools listed: pnpm everyapp mcp show <id> gives the last listing's error, and mcp refresh <id> lists again. MCP.
  • Other answers on an app host: 503 app_unreachable (the Gateway has no working binding to the Worker: status says APP_WORKER_MISSING or APP_NOT_BOUND), 502 app_error (the app's Worker threw: its logs, see When to escalate), 503 app_unavailable with app (a row with a tier or scope this Gateway doesn't know), 404 no_app_for_host. A status this Gateway doesn't know (a newer CLI's) shows as it is and doesn't open.
  • Source: packages/perimeter/src/gateway.ts, apps/every-app-gateway/src/server/app-activation.ts, packages/cli/src/commands/status/problems.ts.

A removed user still has access

  • Symptom: someone removed from an organization, or taken off an app, still gets in for a short while.
  • Cause: the Gateway caches each session's organization membership and each organization's status per isolate for 30 s (ORG_CONTEXT_CACHE_TTL_MS; membership revocation can take up to this 30s TTL to propagate to the perimeter), and an identity token already handed to an app is valid for 120 s (IDENTITY_TTL_SECONDS), so revocation/identity changes converge within that window (docs/TESTING.md). Better Auth's cookie cache is off (session.cookieCache.enabled: false, apps/every-app-gateway/src/auth/shared.ts), so the session row and the user's banned flag are read from D1 on every request: those two windows are the only ones. App access (user_app_access) itself is read on every request.
  • Confirm: wait two minutes and try again.
  • Fix: nothing to run. To cut someone off at once, Deactivate them (Admin → Users → ⋯ → Deactivate): a deactivated (banned) user fails every request and token check, and their tokens and AI connections are revoked (user docs). Source: apps/every-app-gateway/src/server/organization/orgContext.ts, apps/every-app-gateway/src/perimeter/betterAuthAuthenticator.ts, packages/sdk/src/internal/protocol.ts.

Secrets

Secret not declared

  • Symptom: SECRET_NOT_DECLARED, one of: <name> isn't a Gateway setting or one of <app>'s secrets with Did you mean <NAME>? (a known name differing in case or underscores; data.didYouMean, the rerun has it corrected); <name> isn't a Gateway setting, and there's no app here, An app's own secrets are set from the app's directory (where everyapp.config.ts is), or from anywhere with --app <id>., Gateway settings: pnpm everyapp secret list, fix cd into the app's directory and run this again, or add --app <id>; <app> doesn't list <name> as a secret, It isn't a Gateway setting either. (with --app <id>: Its deployed manifest (what --app <id> reads) doesn't list it, and it isn't a Gateway setting either.), fix add it to secrets in everyapp.config.ts: secrets: [...] (with --app, … and deploy <id> again), rerun pnpm everyapp secret set <name>[ --app <id>]. From configure <app> with NAME=value lines piped in: <app> doesn't declare <name> when the name looks like one (upper snake case), otherwise <app> doesn't declare line <n> (not shown: it may be a value); then The piped lines set what its deployed manifest lists (a provider, a capability or one of its secrets). Nothing was set. (in the app's directory, its everyapp.config.ts lists), fix leave it out, or declare it in everyapp.config.ts; a bare value goes to pnpm everyapp secret set NAME (by name, … and deploy again; …); refused before anything is written. A line that isn't NAME=value at all is PIPED_LINE_INVALID (Line <n> of the piped input isn't NAME=value, Nothing was set., fix pipe NAME=value lines, one per line; a bare value goes to pnpm everyapp secret set NAME; the line is never echoed).
  • Cause: a name goes to the Gateway when it is in SETTABLE_GATEWAY_SETTINGS, otherwise to the app named by --app <id> (its deployed manifest, from the registry) or, without the flag, the one whose everyapp.config.ts is in the current directory; either manifest must list it under secrets for set. Refused before any value is read.
  • Confirm: pnpm everyapp secret list <app> (or pnpm everyapp secret list in the app's directory: the app's declared secrets and the Gateway settings it needs; a name absent from both is undeclared); grep -n secrets everyapp.config.ts; data.didYouMean when the name is a near miss.
  • Fix: as the message says. The app must have deployed once (APP_NOT_FOUND: <app> isn't deployed yet, Its secrets can be set after its first deploy. Nothing was changed., rerun pnpm everyapp deploy). Before that deploy, secret list in the app's directory shows each declared secret as after first deploy with <app> isn't deployed yet; its own secrets can be set after that. and pnpm everyapp deploy last in data.next (data.app.deployed: false). data.next is a list: pnpm everyapp configure <app> first, while something the app needs isn't set, then pnpm everyapp deploy for an undeployed app with declared secrets; the key is absent when there is nothing to run. An app that declares no secrets and needs no Gateway setting gets <app> declares no secrets and needs no Gateway settings. and no data.next; secret set of an app secret fails APP_NOT_FOUND until the first deploy, and so does a piped one in configure (<app> isn't deployed yet, Its own <name> can be set after its first deploy. Nothing was set.), which sets the Gateway's settings meanwhile; configure <app> by name needs the deploy too (<app> isn't deployed to this Gateway, rerun pnpm everyapp status; a row whose first deploy never finished, status: "pending", counts as undeployed). Adding a name to secrets needs no deploy before secret set or configure in the app's directory (the manifest is read locally); configure <app> by name reads the deployed one. Source: packages/cli/src/commands/secret/index.ts.

Name reserved or not settable

  • Symptom: SECRET_NAME_RESERVED: <name> is a reserved name, everyapp manages <the reserved names and prefixes>., fix pick another name. SECRET_NAME_INVALID: letters, digits and underscores, starting with a letter. SECRET_NOT_SETTABLE: <name> can't be changed with secret set, It's one of the Gateway's own keys, written once by setup. (or for THIRD_PARTY_DEPLOYS, It's the hosted Gateway's launch gate, opened by hand.); <name> only works in local development, A deployed Gateway doesn't read it., fix set it in the Gateway's .dev.vars (MCP_ACCESS_TOKEN_TTL, DATAFORSEO_SANDBOX). A manifest that lists a Gateway setting under secrets fails validation (INVALID_MANIFEST: <name> is a Gateway setting, which everyapp secret set <NAME> sets on the Gateway for every app; … naming the provider or capability to list instead); one that reuses a binding's name: secrets: <name> is one of the app's bindings; pick another name for the secret; third-party apps: secrets: third-party apps can't have secrets of their own yet.
  • Confirm: the headline names the rule broken; outside an app directory pnpm everyapp secret list --json carries every settable Gateway setting in data.gateway.settable (the table shows what is set, what setup wrote and, of the rest, only what some app here reads, then N more can be set), so a name not there and not under the app's secrets is neither; grep -n '<name>' everyapp.config.ts cloudflare.config.ts shows a binding with that name.
  • Fix: another name, or the setting the message names. Source: packages/cli/src/lib/app/secrets.ts, packages/cli/src/commands/secret/index.ts, packages/perimeter/src/manifest/manifest.ts.

Gateway setting or app secret

  • Symptom: a value was set but the app doesn't see it, or secret list shows it in an unexpected place.
  • Cause: the routing is by name only. A Gateway setting (OPENAI_API_KEY, EMAIL_FROM, the MCP_* and CLOUDFLARE_ACCESS_* names, …) lives on the Gateway Worker for every app and is reached through the Gateway's capabilities, never as env.NAME in the app; an app secret lives on the app's Worker (and its helpers) as env.NAME. Both change the live Worker at once (Live now. No deploy needed.) and survive deploys. With --verbose, secrets.route progress lines say where each name went (<name> is a Gateway setting → Worker every-app-gateway, <name> is <app>'s secret → Worker <worker>).
  • Confirm: pnpm everyapp secret list <app> (from anywhere; columns SETTING, WHERE, STATUS: the app's own secrets and the Gateway settings its manifest needs); outside an app with no id, the Gateway's (SET BY you, setup or not set), then every app's settings. --json carries data.app.workers[].names, data.app.settings, data.app.setup and data.gateway.names.
  • Fix: set it where it belongs; pnpm everyapp configure <app> routes every name for you and its table says where each goes. A partial failure (SECRETS_PARTIALLY_APPLIED: <A> is set; only <B> is left.) reruns only what is left (pnpm everyapp secret set <B>, with --app <id> when it ran by --app and one of the app's own is left); through configure, the rerun is pnpm everyapp configure <app> (to set what is left; before the first deploy pnpm everyapp configure, in the app's directory, to set what is left), which asks only for what is still missing. Secrets, Gateway settings and capabilities. Source: packages/cli/src/commands/secret/index.ts.

Value not visible in dev

  • Symptom: everyapp dev runs the app without a secret that production has, or secret set under --json fails with SECRET_VALUE_REQUIRED (Can't ask for <name>'s value without a terminal, Or pipe it in: echo "$NAME" | pnpm everyapp secret set NAME, rerun pnpm everyapp secret set <name> --from-env <name>); or configure <app> under --json with nothing piped and no --from-env variable found fails the same way (Can't ask for <app>'s settings without a terminal, <app> needs <names>. Run this at a terminal, pipe NAME=value lines in (one per line), or export them under those names and add --from-env (which also takes the variable of any unset optional setting)., data.names the names still missing; rerun pnpm everyapp configure <app> --from-env with rerunNote with the variables exported once deployed, before the first deploy pnpm everyapp configure --from-env with in the app's directory, with the variables exported).
  • Cause: production values never come down; in development the Worker reads .dev.vars in the app's directory. A value is read from a hidden prompt, from stdin when piped, or from --from-env VAR, never from the command line; SECRET_EMPTY ($VAR is empty or not set) and SECRET_SEVERAL_NAMES (one value sets one name) come from that. configure <app> reads NAME=value lines from stdin (one per line; blank lines and # lines skipped, an export prefix allowed, one pair of quotes around a value dropped, the value otherwise as it is) or, with --from-env, each unset setting from the variable of its own name (stdin isn't read then; a set name is never replaced); a line that isn't NAME=value is PIPED_LINE_INVALID (Line <n> of the piped input isn't NAME=value; never echoed, since it may be a bare secret, which goes to pnpm everyapp secret set NAME), a blank piped value is SECRET_EMPTY (<name> is empty in the piped lines), and a value typed after the verb (configure NAME=value) is VALUE_ON_COMMAND_LINE (<name>=… looks like a value on the command line, which configure never takes; nothing is set, and the message says to rotate it if it was real). A second argument after the app id is refused by the argument parser as TOO_MANY_ARGUMENTS with the input cut at its = ("OPENAI_API_KEY=…"), and --from-env=<anything> as INVALID_VALUE (--from-env is a switch and takes no value), both with the line A value never goes on the command line: it lands in the shell's history and the process list. Rotate it if it was real.
  • Fix: put the name in .dev.vars for dev; for an agent, export NAME=… then pnpm everyapp configure <app> --from-env --json (every exported name at once) or pnpm everyapp secret set NAME --from-env NAME --json. Development vs production. Source: packages/cli/src/lib/secretValue.ts.

Dev

Port in use

  • Symptom: DEV_PORT_IN_USE: Port <port> is already in use, rerun with --port <next free port> (data.free). DEV_PORT_UNAVAILABLE: Can't listen on <host>:<port>, data.fix: "pass another --port or --host".
  • Cause: the gateway-lite listens on 127.0.0.1 at --port, else $PORT, else 8787 (--host for another address); the probe before anything starts found the port taken.
  • Confirm: lsof -nP -iTCP:<port> -sTCP:LISTEN (macOS, Linux) names the process holding it (another everyapp dev, a vite or cf dev left running); data.free is the next free port.
  • Fix: the rerun with --port, or stop the other everyapp dev. Stop a running dev with Ctrl+C (SIGINT) to the everyapp process: it signals the dev server's own process group (cf dev, or the manifest's dev command, with the Vite and workerd processes they started), emits the step dev.stop and exits 130, after which pnpm prints its ELIFECYCLE line for exit code 130: that line is pnpm's, not a failure of dev's. Don't pkill -f 'vite|workerd': the pattern hits every other project's dev servers on the machine. Source: packages/cli/src/commands/dev/gatewayServer.ts, packages/cli/src/commands/dev/index.ts, packages/cli/src/commands/dev/appServer.ts, packages/cli/src/lib/interrupt.ts, packages/cli/src/lib/longRunning.ts.

Mirror without a Gateway

  • Symptom: MIRROR_GATEWAY_UNREACHABLE: No local Gateway at http://localhost:3000, See "mirror mode" in docs/TESTING.md., data.fix: "run pnpm dev in apps/every-app-gateway, or pass --gateway <url>". While running, warnings MIRROR_SESSION: No Gateway session yet: log in at http://everyapp.localhost:<port>, then reload, No active organization: create or pick one in the Gateway, then reload, Your Gateway session expired: log in again, then reload, or Can't reach the local Gateway at <url> with Is pnpm dev running in apps/every-app-gateway?. INVALID_DEV_MODE: --mode must be stub or mirror, not "…" (or EVERYAPP_DEV_MODE).
  • Cause: --mode mirror resolves a real session from a separately running local Gateway (/api/dev/identity), probed for 5 s at start.
  • Confirm: curl -s -o /dev/null -w '%{http_code}\n' http://localhost:3000/api/dev/identity (connection refused: nothing listens there); echo $EVERYAPP_DEV_MODE when nobody passed --mode.
  • Fix: pnpm dev in apps/every-app-gateway first, or --gateway <url>; or stay in stub mode (the default: a seeded dev user). Source: packages/cli/src/commands/dev/index.ts, packages/cli/src/commands/dev/remoteAuth.ts, packages/cli/src/commands/dev/mode.ts.

localhost hosts

  • Symptom: MIRROR_BASE_HOST: Mirror mode needs a two-label base host, like everyapp.localhost, The session cookie must cross from the Gateway to the app's subdomain; "<host>" has one label., Open apps at http://<app>.everyapp.localhost:<port> through portless or an /etc/hosts entry.; rerun with --base-host everyapp.localhost; data.fix names the local Gateway's EVERYAPP_DEV_COOKIE_DOMAIN=everyapp.localhost and GATEWAY_URL=http://everyapp.localhost:<port>. A 421 This dev server serves <app>.localhost, not <host>. with the warning DEV_WRONG_HOST: the browser used another host.
  • Cause: the gateway-lite routes by Host: the first label is the app, the rest the base host; in stub mode the app is http://<app-id>.localhost:8787 and the bare base host is the launcher. Mirror mode needs a cookie scoped to a parent domain, impossible on single-label localhost. Listening on anything but loopback warns DEV_EXPOSED (anyone who reaches the port is the dev user: the dev keypair is public). A request that did reach the gateway-lite but failed inside it (the app's dev server refused or dropped the connection) answers 502 and warns once per reason: DEV_REQUEST_FAILED, <METHOD> <path> failed in the local Gateway: <reason>, data.method, data.path, data.status: 502.
  • Confirm: the ready block's App, MCP and Log in URLs against what the browser or client used; curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: <app-id>.localhost' http://127.0.0.1:<port>/ (421 means the Host is wrong, 502 the app's server failed the request).
  • Fix: open the URL the ready block prints (App, MCP, Log in); in mirror mode use everyapp.localhost; for a 502, the app's dev server output on the same terminal says why. Source: packages/cli/src/commands/dev/mode.ts, packages/cli/src/commands/dev/gatewayServer.ts, packages/cli/src/commands/dev/index.ts.

Dev server does not start

  • Symptom: DEV_SERVER_FAILED: <app>'s dev server exited before it was ready, Its output is above. Or DEV_SERVER_TIMEOUT: <app>'s dev server didn't start listening in time, It must listen on the port everyapp passes in PORT., data.appPort, data.fix make its dev command (<dev>) listen on the port in PORT (a manifest with a dev command) or in vite.config.ts, set server.port: Number(process.env.PORT) (a manifest without a dev command).
  • Cause: everyapp starts the app's dev server with PORT set to a port it picked and waits for http://127.0.0.1:<PORT>/ to answer, for 90 s (waitForHttp). The server exited first: a crash, a missing dependency, a build error (DEV_SERVER_FAILED: the server's own output is already on the terminal above the error). It kept running but never listened on that port: it listens on a port of its own (DEV_SERVER_TIMEOUT), the usual case when vite.config.ts hardcodes server.port or the dev command passes --port.
  • Confirm: the server's output above the error; grep -n 'port' vite.config.ts and the manifest's dev command; lsof -nP -iTCP -sTCP:LISTEN | grep node while it runs shows which port it took.
  • Fix: for a crash, fix what its output says and run pnpm everyapp dev again; for the port, server.port: Number(process.env.PORT) in vite.config.ts, or make the dev command read $PORT. Source: packages/cli/src/commands/dev/index.ts, packages/cli/src/commands/dev/proxy.ts.
  • A running dev breaks for a few seconds, then recovers, while nothing in the app changed: the checkout's root postinstall is pnpm -C packages/sdk build (package.json), so a pnpm install anywhere in the checkout rewrites packages/sdk/dist; a deploy does the same, through its app.install step (the package manager's install in the app's directory) and, when it rebinds the Gateway, the release build from the checkout, which starts with pnpm --filter @every-app/sdk build (packages/cli/src/lib/gateway/checkoutRelease.ts). The dev server resolves @every-app/sdk from that dist, so requests fail or reload while the files are rewritten. Finish the install or deploy, then use dev again.

A local migration failed

  • Symptom: MIGRATIONS_FAILED: A local migration failed, cf's message, Or start with --skip-migrations if the local database is already usable., data.fix correct the migration, then run <command> again. Dev doesn't start.
  • Cause: before the app starts, everyapp dev applies the app's pending migrations to the local database cf dev serves (step dev.migrations); one of them failed (a SQL error, a migration written against another state of the database). An app with nothing to apply shows no step.
  • Confirm: the cf message under the headline names the migration and the SQL error; the migrations folder the manifest names.
  • Fix: correct the migration and run pnpm everyapp dev again; --skip-migrations starts without applying anything when the local database is already as the app expects. Source: packages/cli/src/commands/dev/migrations.ts, packages/cli/src/commands/dev/command.ts.

Tools not updating in Claude Code

  • Symptom: defineTools() was edited while everyapp dev runs, but Claude Code still lists the old tools at http://<app-id>.localhost:8787/mcp.
  • Cause: the dev ToolBus re-reads the app's catalog every 2 s, but hands its view no refresh, so for 2026-era clients it declares listChanged: false and holds no notification stream; only 2025-era clients get the session-hash notification (TODO.md, "let Claude Code see edited defineTools() without reconnecting").
  • Fix: reconnect the server in Claude Code (/mcp, or remove and claude mcp add --transport http <name> http://<app-id>.localhost:8787/mcp again). On a deployed Gateway, connected 2026-era clients get notifications/tools/list_changed within about 30 s. MCP. Source: packages/toolbus/src/http.ts, TODO.md.

MCP

A client cannot connect

  • Symptom: an AI client fails to add https://<gateway>/mcp, loops on authorization, or gets 404s from /oauth/*.
  • Cause, one of (docs/runbooks/mcp-oauth.md): OAuth is off (MCP_OAUTH_ENABLED is false, 0, off, no, or anything else unrecognized, which logs mcp.oauth.flag_unrecognized): /oauth/* and the discovery documents answer 404 and /mcp answers 401 with WWW-Authenticate: Bearer realm="every-app" and no authorization server, so only personal access tokens work. Dynamic client registration is off (MCP_DCR_ENABLED=false): the metadata names no registration_endpoint and /oauth/register answers 404, so Cursor, Gemini CLI and MCP Inspector can't connect while claude.ai, Claude Code, ChatGPT (CIMD clients) can; or it is rate-limited (429 rate_limited: 5 a minute per address per isolate, 20 a day, 200 a day for the Gateway; log mcp.oauth.registration_limited). A browser origin isn't allowed: a page on another origin needs it in MCP_ALLOWED_ORIGINS (http://localhost:6274 for MCP Inspector's direct mode). The resource doesn't match: the protected resource metadata's resource must equal exactly what the user typed, https://<gateway>/mcp, so GATEWAY_URL must have no path; a refresh with another resource answers invalid_target and the client must connect again. The consent page says it couldn't verify the app: its metadata document couldn't be fetched (mcp.oauth.cimd_failed).
  • Confirm: curl -si -X POST https://<gateway>/mcp | grep -i www-authenticate (expect Bearer realm="OAuth", resource_metadata="https://<gateway>/.well-known/oauth-protected-resource/mcp", scope="mcp:read mcp:write"); curl -s https://<gateway>/.well-known/oauth-protected-resource/mcp (resource exactly https://<gateway>/mcp); curl -s https://<gateway>/.well-known/oauth-authorization-server (issuer, client_id_metadata_document_supported); Worker logs mcp.oauth.error. A browser-based client that gets a 403 JSON-RPC error Forbidden: origin not allowed. (code -32000) sent an Origin that is neither the Gateway's own nor in MCP_ALLOWED_ORIGINS (comma or space separated): that is the origin refusal, and the page's origin goes in the setting (packages/toolbus/src/http.ts, apps/every-app-gateway/src/server/toolbus/gateway-mcp.ts; locally, everyapp dev also answers Forbidden: invalid Host header. to a non-localhost Host).
  • Fix: pnpm everyapp secret set MCP_OAUTH_ENABLED / MCP_DCR_ENABLED with true; set MCP_ALLOWED_ORIGINS; then claude mcp add --transport http everyapp https://<gateway>/mcp and authenticate. A person's steps: Use your apps from Claude or ChatGPT. MCP, MCP OAuth runbook.

Personal access token rejected

  • Symptom: a request with Authorization: Bearer epat_… gets 401, or 403 token_scope / no_app_access.
  • Cause: a reserved epat_ bearer fails closed and never falls back to a cookie. 401: the token is unknown, revoked or expired; its owner is no longer a member of its organization or is deactivated; it has a provider: scope; or it is a deploy key (deploy scope: a token for the Gateway's deploy API, made in Admin → Tokens; it opens no app). 403 no_app_access: a token bound to one app used on another's host, or the member lacks the app. 403 token_scope: a token whose scopes are all mcp: scopes on any route but /mcp, on every route of a host whose manifest has no mcp, or on /mcp a scope other than *, mcp:write or mcp:read. An app host serves /mcp only when its manifest says mcp: { by, why } (Manifest); without it the path is the app's own private route (usually 404), and a client pointed at https://<app-host>/mcp must connect https://<gateway>/mcp instead. Where it is served, it takes personal tokens and the app's own session only, never an OAuth token (its 401 says Use a personal access token here, or connect https://<gateway>/mcp), and answers 503 { "error": "mcp_unavailable" } while the Gateway has no catalog to serve for it.
  • Confirm: the body's error and reason; the token under Admin → Tokens (kind, scopes, bound app).
  • Fix: a new token with the right scopes, or the Gateway's /mcp with OAuth. Source: apps/every-app-gateway/src/perimeter/betterAuthAuthenticator.ts, packages/perimeter/src/gateway.ts, docs/security-model.md ("Agent Tools (MCP)").

Tools missing

  • Symptom: a tool the app defines isn't in tools/list, or a call answers Unknown tool; an app's tools are absent altogether.
  • Cause: tools with risk: "destructive" are hidden from every caller until approvals exist (reason destructive_requires_approval; TODO.md, "Destructive tools"); write tools (a tool without a risk counts as write) are hidden from read-only connections and mcp:read tokens (read_only_connection); every tool is hidden when the organization is blocked (org_blocked, checked before the other two); hidden means absent from the list and Unknown tool on a call. The Gateway's /mcp serves only the active apps of the caller's organization they have access to, narrowed to the apps an OAuth connection chose or the one app a token is bound to; an app that needs setup (a required setting unset) serves no tools, and everyapp_list_apps names it with needs setup: an admin runs pnpm everyapp configure <app>; an MCP app that is on (pnpm everyapp mcp on dataforseo) adds its dataforseo_<tool> tools once DATAFORSEO_API_KEY is set. A catalog is taken when an app is activated, on first use, and when its host sees a new version; an app that defines no defineTools() catalog has none.
  • Confirm: call everyapp_list_apps and everyapp_whoami; check the tool's risk in the app's tools.ts; the connection's access (read-only or read & write) under AI connections; the member's app access in Admin → Apps.
  • Fix: grant the app, reconnect with write access, or change the tool's risk. Source: packages/toolbus/src/policy.ts, apps/every-app-gateway/src/server/toolbus/gateway-mcp.ts, docs/security-model.md.

Behind Cloudflare Access

  • Symptom: an MCP client gets an HTML login page; an AI app loops on 401 invalid_token; 403 not_connected; a script's personal token gets Access's 401.
  • Cause and fix (docs/runbooks/cloudflare-access.md, "Troubleshooting"): an HTML page means a bypass path is missing on the outer ring (/mcp, /mcp/*, /oauth/token, /oauth/register, /.well-known/* on the Gateway's application; /mcp, /mcp/* and each app's public routes on the apps' application) or Managed OAuth isn't on. For the 401 loop read WWW-Authenticate: one naming cloudflare-access-protected-resource is Access's (the person isn't allowed by the policy or identity provider); one naming oauth-protected-resource (or realm="every-app" with no metadata) is the Gateway's: it didn't take the assertion; logs mcp.access_assertion_ignored with reason invalid (wrong CLOUDFLARE_ACCESS_AUD or team domain, or access.jwks_fetch_failed) or audience (the apps' application is in front of the Gateway host); no such line means the outer ring isn't configured (CLOUDFLARE_ACCESS_TEAM_DOMAIN, CLOUDFLARE_ACCESS_AUD) or the Gateway isn't self_hosted. 403 not_connected: Access knows the person, the Gateway doesn't: open the Gateway in a browser and sign in once through Access, then connect again. A personal token behind Managed OAuth needs a Service Auth policy and the CF-Access-Client-Id / CF-Access-Client-Secret headers alongside (not yet checked against a live Managed OAuth application). Cloudflare Access, the runbook.

Access

pnpm everyapp access on|off|show (packages/cli/src/commands/access/, packages/cli/src/lib/access/) sets the outer ring up, takes it down and reports it; the entries below name it where it is the fix. Cloudflare Access documents the command, the user docs' Access page is the person's view, packages/cli/skills/everyapp/setup/access.md (in an app, .claude/skills/everyapp/setup/access.md) is the step list for an agent, and docs/runbooks/cloudflare-access.md (mirrored) is the runbook it automates. What the ring adds to the trust boundaries is in /docs/agent/repo/threat-model (mirrored from docs/threat-model.md).

Locked out

  • Symptom: nobody can open the Gateway: Access refuses, or the Gateway says This gateway signs people in through Cloudflare Access, and this request didn't come through it., or You're signed in to Cloudflare Access as <x>, but this account is <y>.
  • Cause: the outer ring's settings are Worker secrets (CLOUDFLARE_ACCESS_TEAM_DOMAIN, CLOUDFLARE_ACCESS_AUD, CLOUDFLARE_ACCESS_APPS_AUD), and with CLOUDFLARE_ACCESS_TEAM_DOMAIN and CLOUDFLARE_ACCESS_AUD both set no session counts without Access's word for that person (one of the two alone, a team domain that isn't <team>.cloudflareaccess.com, or a CLOUDFLARE_ACCESS_AUD that isn't an AUD tag leaves the ring off and logs access.config_problem: apps/every-app-gateway/src/auth/cloudflare-access/config.ts). The first message means the request carried no assertion Access signed for one of the AUD tags: Access isn't in front of that hostname, the path is bypassed, or the tag is wrong. The second means the account's email isn't the one Access asserts (behind the outer ring an account must use the address Access knows).
  • Confirm: pnpm everyapp access show --json: data.settings.names (which of the three are set on the Worker) and data.settings.current (false when the settings name an application that no longer exists: the human output says name an application that no longer exists: nobody can sign in), data.applications.gateway / data.applications.apps with their inSync and policyAttached; pnpm everyapp secret list for the names only (values can't be read back).
  • Fix: pnpm everyapp access off removes CLOUDFLARE_ACCESS_AUD, CLOUDFLARE_ACCESS_APPS_AUD and CLOUDFLARE_ACCESS_TEAM_DOMAIN in one new version of the Gateway Worker (passwords sign people in again at once), then deletes the two Access applications (one that also covers other hosts only loses the Gateway's and the apps' destinations; the team, its identity providers and the policy stay; it asks first, --yes unattended: Turning Access off needs confirmation at a terminal, or --yes). pnpm everyapp access off --keep-applications removes only the three settings: Access still asks people to sign in at the hostnames, and the Gateway no longer requires it. pnpm everyapp secret unset CLOUDFLARE_ACCESS_AUD is the one-setting form (the ring needs both CLOUDFLARE_ACCESS_TEAM_DOMAIN and CLOUDFLARE_ACCESS_AUD, so it is off either way). Without a checkout: npx cf@1.0.0-beta.14 workers secrets delete CLOUDFLARE_ACCESS_AUD --worker every-app-gateway, and later pnpm everyapp setup at a terminal (it asks before replacing a version it didn't upload: rerun_interactive). Then pnpm everyapp access on puts the ring back once the cause is fixed. For the wrong person: Not you? Sign out of Access on the sign-in page (or /cdn-cgi/access/logout) and sign in there as the account's email; for an owner account with the wrong email, change it in D1 (npx cf@1.0.0-beta.14 d1 query <id> --sql "UPDATE users SET email = '<x>' WHERE email = '<y>'"), then sign in with the password once through Access to link it. Source: docs/runbooks/cloudflare-access.md, packages/cli/src/commands/access/index.ts, packages/cli/src/commands/access/command.ts, packages/cli/src/commands/secret/index.ts.

A member is not let through

  • Symptom: You don't have an account on this gateway yet.; apps send people back to the sign-in page (the page you were going to sent you back here); teammates can't log in at Access at all; the 51st person is blocked; an app's sync over WebSockets breaks with 403; a new app's public route is behind Access's login.
  • Cause: Access lets a person through only when the allow policy names their email (pnpm everyapp access on adds every member's and pending invitee's email, and never removes anyone); the Gateway then needs an account for that email, or a way to create one. Invited after the last access on: Access doesn't know them yet. No account and no invitation: You don't have an account on this gateway yet. Apps bouncing: the apps' application's AUD tag isn't in CLOUDFLARE_ACCESS_APPS_AUD (an application recreated in the dashboard has a new tag). Nobody can log in at Access: the Zero Trust organization's only provider is Sign in with Cloudflare, restricted to the Cloudflare account's members (no one-time PIN provider; pnpm everyapp access on adds it, on the applications too). Access's forbidden page after Sign in with Cloudflare: that Cloudflare account's email isn't a Gateway member (ACCESS_LOGIN_NOT_MEMBER); use the emailed code, or invite that address. The 51st: the Free plan has 50 seats. WebSocket 403: the Access application is attached to the Worker ("Protect this Worker") instead of hostname destinations. A new app's public route is behind Access's login: the apps' application lacks the bypass for that app's host and paths.
  • Confirm: pnpm everyapp access show (or --json): who the policy lets in against the members and invitees (data.policy.notYet names the members and invitees the policy doesn't let in yet), whether the applications' public paths match the apps' public routes (data.applications.apps.inSync, data.applications.gateway.inSync; data.desired.apps is what on would write) and carry the policy (policyAttached), the sign-in methods, and data.settings.current (false when the settings name applications that no longer exist); data.fixes is ["pnpm everyapp access on"] whenever any of those is off, and absent otherwise; data.linkPending names members who have not signed in through Access yet.
  • Fix: pnpm everyapp access on, again, after inviting someone (it adds them to the policy) or after a change by hand: it changes only what differs, keeps both Sign in with Cloudflare and the one-time PIN (each created when the team lacks it) and allows them on both applications (nothing else of the team's), puts hostname destinations in front, writes each app's public routes as path overrides (deploys keep those in step themselves), and rewrites the three settings when the applications it finds aren't the ones it recorded (a recreated application's new AUD tag); pnpm everyapp access show says when they are stale, and --write-settings rewrites them regardless. For people without an invitation, add their domain to CLOUDFLARE_ACCESS_AUTO_JOIN_DOMAINS (exact match; acme.com doesn't cover eng.acme.com; access on --email-domain <domain> lets them through Access, and the command's output names this setting) or set CLOUDFLARE_ACCESS_OUTER_RING_AUTO_JOIN=everyone on the outer ring. Someone taken out of the Access policy keeps their tokens and AI connections until deactivated (tokens never pass through Access): Deactivate them in Admin → Users. Source: docs/runbooks/cloudflare-access.md, packages/cli/src/commands/access/index.ts, packages/cli/src/commands/access/command.ts.

Password accounts

  • Symptom: An account with this email already exists. on Continue with Company SSO; No "Continue with" button; a member can't sign in after EMAIL_PASSWORD_SIGN_IN was set to false.
  • Cause: an Access identity links to an existing account only when that account's email is verified; accounts created with a password (the owner's from the setup link included) aren't, so each connects once while signed in: behind the outer ring, sign in with the password once through Access; with OIDC, sign in with the password on that page, or use Connect Company SSO in the account menu. No button: a missing or malformed OIDC setting turned it off (access.config_problem in the Worker's logs). EMAIL_PASSWORD_SIGN_IN=false takes effect only while an Access method is configured (else it is ignored and logged: EMAIL_PASSWORD_SIGN_IN=false is ignored: no Cloudflare Access sign-in is configured, so turning passwords off would lock everyone out., apps/every-app-gateway/src/auth/cloudflare-access/config.ts) and an owner has connected an Access identity (ownerHasAccessSignIn(), apps/every-app-gateway/src/auth/cloudflare-access/membership.ts); then nobody signs in, signs up or resets a password over HTTP, and members who never connected Access can't sign in until it is true again.
  • Confirm: pnpm everyapp access show --json data.linkPending names the members who have not connected an Access identity yet (access on prints the same list as who signs in once with their password, data.signIn.pending; readPeople in packages/cli/src/commands/access/index.ts: accounts with a credential row and no cloudflare-access row); pnpm everyapp secret list shows whether EMAIL_PASSWORD_SIGN_IN and the CLOUDFLARE_ACCESS_* / OIDC settings are set; the Worker's logs for access.config_problem.
  • Fix: have everyone sign in with Access once before turning passwords off; pnpm everyapp secret set EMAIL_PASSWORD_SIGN_IN with true to turn them back on. Source: docs/runbooks/cloudflare-access.md, packages/cli/src/commands/access/index.ts.

Production verification and rollback

From docs/TESTING.md, section C (mirrored):

  • Probes: curl -sS https://<worker>.<account>.workers.dev/ fails (workers.dev is off); a forged x-everyapp-identity header at any entry that isn't the Gateway gets 401; a private route without a session gets 401 (302 for an HTML navigation); a declared public route gets 200; POST https://<host>/__everyapp/tools/call gets 404; curl -sI https://<host>/ shows HSTS and nosniff on any answer; the CSP with frame-ancestors 'none' is on HTML only, so look for it on a page an anonymous caller can render (a declared public route, or https://<gateway-host>/sign-in): a private route's anonymous 401 JSON and 302 carry none. Referrer-Policy is strict-origin-when-cross-origin on app hosts and no-referrer on the control plane.
  • Registry: pnpm everyapp status --json data.apps (each app's status and whether the live Gateway binds it).
  • Roll back a bad Gateway release: npx cf workers deployments list --worker every-app-gateway, then npx cf workers deployments create --worker every-app-gateway --strategy percentage --versions '[{"version_id":"<VERSION_ID>","percentage":100}]'. pnpm everyapp db restore --gateway takes the Gateway's database back to the restore point saved before its last migration (--json gives cf's own time-travel restore command); Gateway migrations only add, so the previous version runs against the migrated database. That rollback is a stopgap: the install row still names the release everyapp put live, so the next Gateway deploy or rebind puts the checkout's Gateway back, and since the Worker then serves a version everyapp didn't record, an unattended run stops with rerun_interactive. For a lasting rollback, check out the good commit and run pnpm everyapp setup --allow-downgrade. Apps are unaffected (their Workers and the registry don't change); identity tokens live 120 s, so identity changes converge within that window.

When to escalate

  • Run the failing command again with --verbose (and --json for the raw events). It shows every step, the error's code, an unexpected error's stack frames (UNEXPECTED: everyapp hit an unexpected error, This is a bug in everyapp, not something you did.; without --verbose it suggests --verbose for a stack trace to report; under --json the stack is in data.stack either way), the output of the children everyapp ran, and cf attached to the terminal.
  • Read the log file the error names (Full log: $TMPDIR/everyapp/<name>-<time>-<pid>.log, data.log) for a failed install or build; cf's last lines are in the error itself.
  • For a problem on a live host (a 502 app_error, an AI or email refusal you can't place), read the Worker's own logs, which everyapp never prints: in the Cloudflare dashboard, Workers & Pages → every-app-gateway (or the app's own Worker) → Observability (Logs on older accounts) → Live streams them, and the stored Workers Logs are on the same page; from a terminal, npx wrangler tail every-app-gateway (cf has no tail; wrangler asks for its own one-time login). Step by step in the skill's setup/gateway.md, "Verify".
  • Bring it to the Discord (the user docs' channel). Include: the exact command line, pnpm everyapp --version, the error or action_required event (code, message, data) or the --verbose output, pnpm everyapp status --json (its data.fixes, data.release.version, data.checkout.version, data.mode), the log file's tail, the Node version, and for an app problem the response body and headers (error, reason, x-everyapp-error). Never include a setup link, a personal access token, a deploy key or a secret's value; cf's output and the log file hold none, but a build's own output may.

Sources

  • landing-page/content/docs/troubleshooting.mdx
  • landing-page/content/docs/people.mdx
  • landing-page/content/docs/gateway.mdx
  • packages/cli/src/commands/status/problems.ts
  • packages/cli/src/commands/status/index.ts
  • packages/cli/src/lib/errors.ts
  • packages/cli/src/lib/output/events.ts
  • packages/cli/src/lib/output/report.ts
  • packages/cli/src/lib/output/run.ts
  • packages/cli/src/lib/output/prompt.ts
  • packages/cli/src/lib/childLog.ts
  • packages/cli/src/lib/cf/account.ts
  • packages/cli/src/lib/cf/deploy.ts
  • packages/cli/src/lib/cf/errors.ts
  • packages/cli/src/lib/cf/plan.ts
  • packages/cli/src/lib/cf/runner.ts
  • packages/cli/src/lib/cf/toolchain.ts
  • packages/cli/src/lib/cf/d1.ts
  • packages/cli/src/lib/runtime.ts
  • packages/cli/src/lib/checkout.ts
  • packages/cli/src/lib/manifest.ts
  • packages/cli/src/bin/cli.ts
  • packages/cli/src/lib/domains/preflight.ts
  • packages/cli/src/lib/domains/zones.ts
  • packages/cli/src/lib/domains/certificates.ts
  • packages/cli/src/lib/domains/certWait.ts
  • packages/cli/src/lib/domains/localDns.ts
  • packages/cli/src/lib/domains/https.ts
  • packages/cli/src/lib/gateway/lease.ts
  • packages/cli/src/lib/gateway/apply.ts
  • packages/cli/src/lib/gateway/discover.ts
  • packages/cli/src/lib/gateway/connect.ts
  • packages/cli/src/lib/gateway/install.ts
  • packages/cli/src/lib/gateway/reconcile.ts
  • packages/cli/src/lib/gateway/release.ts
  • packages/cli/src/lib/gateway/setup-link.ts
  • packages/cli/src/lib/gateway/secretsFile.ts
  • packages/cli/src/lib/gateway/registry.ts
  • packages/cli/src/commands/setup/runSetup.ts
  • packages/cli/src/commands/setup/deployGateway.ts
  • packages/cli/src/commands/appGateway.ts
  • packages/cli/src/commands/deploy/index.ts
  • packages/cli/src/commands/deploy/deployApp.ts
  • packages/cli/src/commands/deploy/steps/checkSchemaDrift.ts
  • packages/cli/src/commands/deploy/steps/waitForLive.ts
  • packages/cli/src/commands/deploy/steps/offerGateway.ts
  • packages/cli/src/commands/dev/index.ts
  • packages/cli/src/commands/dev/mode.ts
  • packages/cli/src/commands/dev/gatewayServer.ts
  • packages/cli/src/commands/dev/remoteAuth.ts
  • packages/cli/src/commands/dev/migrations.ts
  • packages/cli/src/commands/dev/ready.ts
  • packages/cli/src/commands/dev/command.ts
  • packages/cli/src/commands/dev/proxy.ts
  • packages/cli/src/commands/secret/index.ts
  • packages/cli/src/commands/email/index.ts
  • packages/cli/src/commands/db/restore.ts
  • packages/cli/src/commands/db/index.ts
  • packages/cli/src/commands/create/index.ts
  • packages/cli/src/commands/setup/command.ts
  • packages/cli/src/commands/access/index.ts
  • packages/cli/src/commands/access/command.ts
  • packages/cli/src/lib/access/plan.ts
  • packages/cli/src/lib/access/record.ts
  • packages/cli/src/lib/access/sync.ts
  • packages/cli/src/lib/cf/access.ts
  • packages/cli/src/lib/interrupt.ts
  • packages/cli/src/lib/longRunning.ts
  • packages/cli/src/commands/dev/appServer.ts
  • package.json
  • packages/cli/src/lib/app/cloudflareConfig.ts
  • packages/cli/src/lib/app/workflows.ts
  • packages/cli/src/lib/app/buildOutput.ts
  • packages/cli/src/lib/gateway/checkoutRelease.ts
  • packages/cli/src/lib/package-manager.ts
  • packages/cli/src/lib/git.ts
  • packages/cli/src/lib/domains/hostnames.ts
  • packages/cli/src/lib/cf/privateDir.ts
  • packages/cli/src/lib/migrations/prisma.ts
  • packages/perimeter/src/registry/registration.ts
  • packages/perimeter/src/manifest/deploy.ts
  • apps/every-app-gateway/src/server/deploy/metadata.ts
  • apps/every-app-gateway/src/server/deploy/release.ts
  • apps/every-app-gateway/src/server/deploy/bundle.ts
  • apps/every-app-gateway/src/server/deploy/cfRestDeployer.ts
  • apps/every-app-gateway/src/server/deploy/pipeline.ts
  • apps/every-app-gateway/src/server/deploy/run.ts
  • apps/every-app-gateway/src/server/deploy/registry.ts
  • apps/every-app-gateway/src/server/deploy/browser-install.ts
  • apps/every-app-gateway/src/server/deploy/install.ts
  • apps/every-app-gateway/src/server/repositories/DeployJobRepository.ts
  • apps/every-app-gateway/src/server/capabilities/members.ts
  • apps/every-app-gateway/src/server/capabilities/oauthClient.ts
  • apps/every-app-gateway/src/server/toolbus/gateway-mcp.ts
  • packages/cli/src/lib/app/policy.ts
  • packages/cli/src/lib/app/installBuildOutput.ts
  • packages/cli/src/lib/app/resources.ts
  • packages/cli/src/lib/app/ownership.ts
  • packages/cli/src/lib/app/deploySecrets.ts
  • packages/cli/src/lib/app/buildApp.ts
  • packages/cli/src/lib/app/release.ts
  • packages/cli/src/lib/app/secrets.ts
  • packages/cli/src/lib/app/emailRoutes.ts
  • packages/cli/src/lib/secretValue.ts
  • packages/cli/src/lib/migrations/remote.ts
  • packages/cli/src/lib/migrations/postgres.ts
  • packages/cli/src/lib/release/github.ts
  • packages/cli/src/lib/release/fetch.ts
  • packages/perimeter/src/gateway.ts
  • packages/perimeter/src/csrf.ts
  • packages/perimeter/src/serverFunctions.ts
  • packages/perimeter/src/publicRoutes.ts
  • packages/perimeter/src/registry/apps.ts
  • packages/perimeter/src/manifest/manifest.ts
  • packages/sdk/src/internal/identity.ts
  • packages/sdk/src/internal/protocol.ts
  • packages/sdk/src/server/everyApp.ts
  • packages/sdk/src/server/sendEmail.ts
  • packages/toolbus/src/policy.ts
  • packages/toolbus/src/http.ts
  • apps/every-app-gateway/src/server/app-activation.ts
  • apps/every-app-gateway/src/server/platform-policy.ts
  • apps/every-app-gateway/src/server/app-gateway-entrypoint.ts
  • apps/every-app-gateway/src/server/ai-proxy.ts
  • apps/every-app-gateway/src/server/capabilities/refusal.ts
  • apps/every-app-gateway/src/server/capabilities/email.ts
  • apps/every-app-gateway/src/server/organization/orgContext.ts
  • apps/every-app-gateway/src/server/deploy/auth.ts
  • apps/every-app-gateway/src/server/deploy/errors.ts
  • apps/every-app-gateway/src/server/deploy/routes.ts
  • apps/every-app-gateway/src/perimeter/betterAuthAuthenticator.ts
  • apps/every-app-gateway/src/client/lib/appStatus.ts
  • apps/every-app-gateway/src/auth/shared.ts
  • apps/every-app-gateway/src/auth/cloudflare-access/config.ts
  • apps/every-app-gateway/src/auth/cloudflare-access/membership.ts
  • apps/every-app-gateway/src/auth/cloudflare-access/plugin.ts
  • apps/every-app-gateway/src/client/sign-in-methods.ts
  • docs/runbooks/key-rotation.md
  • docs/runbooks/cloudflare-access.md
  • docs/runbooks/mcp-oauth.md
  • docs/runbooks/email-sending-setup.md
  • docs/runbooks/hosted-install.md
  • docs/TESTING.md
  • docs/security-model.md
  • docs/capabilities.md
  • TODO.md

On this page

How a problem is reportedWhat status reportsThe user docs listSetup and loginWorkers PaidNo domain in the accountZone not activeWorkers subdomainSeveral Cloudflare accountsLogin failed or token rejectedOwner link lost or expiredSetup stopped halfwayDowngrade refusedLease held by another deployNode too oldBunFrozen install refusedNot in the checkoutNot in the app directorynpx everyappGateway build or install failedBuild fails with connect ECONNREFUSED 127.0.0.1 on LinuxChoosing or changing the Gateway's addressEvery command refuses to startDomains and TLSAddress already points somewhereDNS unreadableCAACustom domain limitCertificate pendingA new address does not loadDeploysSchema driftMigration failedPolicy refusedResource name taken, owned or replacedApp id or hostname taken on this GatewayHyperdrive origin missingPostgres URL missingDurable Object removed or movedWorkflow removedApp not live after deployGateway outdated or upgrading during a deployRelease not found, no release, too large or corruptThe Gateway's deploy API and browser installsRunning apps401 or 403 from an appGateway keys rotated or lostA public route that is not publicCSRF or server function guard blocking a requestAI or capability call refusedEmail not sent or not receivedApp statesA removed user still has accessSecretsSecret not declaredName reserved or not settableGateway setting or app secretValue not visible in devDevPort in useMirror without a Gatewaylocalhost hostsDev server does not startA local migration failedTools not updating in Claude CodeMCPA client cannot connectPersonal access token rejectedTools missingBehind Cloudflare AccessAccessLocked outA member is not let throughPassword accountsProduction verification and rollbackWhen to escalateSources