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: …andRun: <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) oraction_required(action,data.url,data.code,data.rerunordata.choices,data.waiting,data.needsTerminal:truewhen only a person at a terminal can do the rerun, so an agent handsdata.rerunto a person instead of running it again; under--jsonit 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 withdata.authorize: truecarries 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 carriesdata.rollbackCommand(cf's ownd1 time-travel restore). --verboseadds 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 runscf deployattached 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 warningCUSTOM_DOMAIN_OWNER_UNCHECKEDshows only with--verbose.- The log file: the children everyapp runs itself (pnpm, a manifest's
installorbuildcommand, 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 andFull 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_FAILEDprintscf 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., withdata.stack),NETWORK_ERROR(retryable),SYSTEM_ERROR,CF_TIMEOUT(retryable),CF_OUTPUT,INVALID_MANIFEST, andCF_<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 inpackages/cli/src/lib/cf/errors.ts). A cf failure of thefree_plankind that no raise site translated never becomesCF_FREE_PLAN:reportFailureintercepts it first and reportsaction_requiredupgrade_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.urlhttps://dash.cloudflare.com/?to=/:account/workers/plans,data.rerunthe same command).CF_ABORTEDmeans cf asked a question nobody could answer and printedAborted.: 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_requiredwithaction: "upgrade_workers_paid". From setup, before anything is created:Subscribe this account to Workers Paid, detailIt'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 PaidwithCloudflare refused the upload on the Free plan.(data.cloudflareholds cf's message), orCloudflare refused, for the Free plan: …. A softer form is the warningWORKERS_PAID_UNVERIFIED(Couldn't confirm this account is on Workers Paid): setup goes on. - Cause:
cf accounts subscriptions getnames a Workers Free subscription (packages/cli/src/lib/cf/plan.ts), or cf's upload error matchedWorkers Paid|paid plan(a matchpackages/cli/src/lib/cf/errors.tsmarks 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 asetup.planskip 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.urlhttps://dash.cloudflare.com/<account id>/workers/plansanddata.plan: "free". From an upload cf refused: the same URL, withdata.workerNameanddata.cloudflare(cf's message) and noplan. From the report-level form (Cloudflare refused, for the Free plan: …):data.urlhttps://dash.cloudflare.com/?to=/:account/workers/plans, noplan. - 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 (deployOrRepairGatewayrunsensureGatewayResourcesand writes the row beforecfDeploy), 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_requiredadd_domain:Add a domain to this Cloudflare account, detailEveryApp 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.urlhttps://dash.cloudflare.com/<account id>/domains/overview. With--domainunder a domain the account doesn't hold:ZONE_NOT_FOUND,<domain> isn't under a domain in this Cloudflare account, listingThis 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 whoamifor 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_requiredadd_domain:Finish activating <zone> on Cloudflare, detailIts nameservers don't point at Cloudflare yet.,data.zone,data.status,data.urlhttps://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, fixfinish its nameserver setup (Cloudflare dashboard → <zone> → Overview). - Cause: the zone's
statusisn'tactive(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.devsubdomain yet:action_requiredregister_workers_subdomain:Choose a workers.dev subdomain for this Cloudflare account, detailCloudflare needs one before an account's first Worker. Apps never use it.;data.urlis the link cf printed, elsehttps://dash.cloudflare.com/<account id>/workers/onboarding. - Cause:
cf deployfailed with cf'ssubdomain_missingkind (its message matchesworkers.dev subdomain|/workers/onboarding;packages/cli/src/lib/cf/errors.tsmarks 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
statussaysGATEWAY_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_requiredchoose_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 }, plusgatewayUrlfor a Gateway);datahas norerunkey at all (the error'srerunis null andreportFailureaddsrerunonly 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 Gatewaynaming where the login's Gateway is; orNo Gateway in <account>, the only account this login seeswith rerunpnpm 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.; orEVERYAPP_GATEWAY_URL must be a full URL, not "<value>", fixset it to https://<value>, or unset it), andaction_requiredinstall_gateway(status:No Gateway in <account>'s Cloudflare account (logged in as <email>), rerunpnpm 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., rerunpnpm 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-gatewayper account); an account cf refuses or doesn't know counts as having none. A CI token scoped to one account lists none, soCLOUDFLARE_ACCOUNT_IDnames it. - Confirm:
cf auth whoamilists the accounts;data.accounts/data.choices. - Fix: run one of
data.choices[].rerun; or pass--account-id <id>/ setCLOUDFLARE_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(rerunpnpm everyapp login), or under--jsonCloudflare 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_requiredcloudflare_login,Log in to Cloudflare with pnpm everyapp login,Unattended? Set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID instead. - Cause:
cf auth whoamisaysauthenticated: falseafter the login ran; orCLOUDFLARE_API_TOKENis set and cf isn't authenticated with it (the CLI never logs in while the variable is set). - Confirm: with
--json, the device login isaction_requiredcloudflare_loginwithdata.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. Checkecho $CLOUDFLARE_API_TOKENis 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, plusCLOUDFLARE_ACCOUNT_ID. Source:packages/cli/src/lib/cf/account.ts.
Owner link lost or expired
- Symptom: a person lost the
Open this link to create the owner accountlink, it says it expired, orstatusreportsNO_OWNER(No owner yet, so nobody can sign in,Apps still deploy.). Under--json, the link isaction_requiredclaim_ownerwithdata.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 lastaction_requiredclaim_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 probeshttps://<gateway>/(checkReachable) and warnsGATEWAY_UNREACHABLE(<host> doesn't answer over HTTPS yet,… The link below works as soon as it does.,data.kind,data.reason) orACCESS_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: warningOWNER_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 isrerunpnpm everyapp setup,rerunNoteat 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.ownernull,data.claimedfalse, aNO_OWNERfix. - Fix:
pnpm everyapp setupagain, at a terminal (it opens the browser) or with--jsonrelayed 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:
statussaysGATEWAY_NOT_INSTALLED,GATEWAY_NOT_UPLOADED,GATEWAY_CHANGEDorBINDINGS_DIFFER(table above); an app command fails withGATEWAY_NOT_INSTALLED(The Gateway at https://<host> isn't finished installing,Setup stopped before its first upload., orThe Gateway's install didn't finish,Setup stopped before it recorded the install.); or an unattended run stops withaction_requiredrerun_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.(rerunNotein 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.,rerunNotein a terminal, and confirm cf's prompt,data.needsTerminal: true.data.needsTerminaltells 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 withDEPLOY_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 querywithout--local,db restore,delete,access,mcp on|off|list) logs in to Cloudflare first (under--jsonthe device login isaction_requiredcloudflare_login: Login failed or token rejected) and then needs an installed Gateway: with none in any account the login sees,statusends withaction_requiredinstall_gateway(rerunpnpm everyapp setup,rerunNoteinstalls one,data.accounts) and the others withGATEWAY_NOT_FOUND(No Gateway in <account>, the only account this login sees, rerunpnpm everyapp setup --domain <hostname>); onlydeployoffers to install one. A Gateway whose install row is missing, or whose row never recorded its first upload, isGATEWAY_NOT_INSTALLEDfor every command butstatus(the two messages above);statusfails on neither: a missing row endsdonewithdata.installed: falseand theGATEWAY_NOT_INSTALLEDfix, a row with no first upload withdata.installed: trueand theGATEWAY_NOT_UPLOADEDfix.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_idnull) 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.versionIdagainstdata.recordedVersionId,data.inSync,data.liveAppsagainstdata.desiredApps. - Fix:
pnpm everyapp setupagain (resumes; a repair redeploys with the registry's apps). Forrerun_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 versionThis checkout's Gateway (<version> from <Mon D>) is older than the live one (from <Mon D>).data.live,data.candidate.statussaysCHECKOUT_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.upToDatefalse,data.checkout.versionagainstdata.release.version, aCHECKOUT_BEHINDfix. - Fix:
git pull(andpnpm install), then the same command (rerunNote: "after that"). Onlysetuptakes--allow-downgrade, offered withdata.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
progresslineWaiting for a deploy of <app> to finish (<email> on <machine>, started 1m ago)for up to 5 minutes, thenLEASE_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>.(orCouldn'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 addsThis 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). WarningLEASE_HEARTBEAT: a renewal failed (Can't reach Cloudflare to keep hold of <target>). - Cause: deploys are serialized by a row in
_everyapp_deploy_leasein the Gateway's D1 (gateway, orapp:<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 --jsondata.leases[](purpose,app,deployer,machine,acquiredAt,expiresAt); theDEPLOY_RUNNINGfix. - 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., fixinstall Node 22 or 24, then run the command again. OrNODE_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 rawUnknown file extension ".ts"from a build means the same thing reached cf. - Cause:
bin/cli.tsrefuses a Node below22.18.0before any command runs. Inside an app, the package manager may run tools on another Node (a Volta pin, pnpm'suse-node-version), whichcf build/cf devwould use. - Confirm:
node -p process.versions.node; in the app's directorypnpm exec node -p process.versions.node; look forvolta.nodeinpackage.jsonanduse-node-versionin.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 commandwith Node.js 22.18.0 or newer. - Cause:
process.versions.bunis set: everyapp was started withbunorbunx. - Confirm: the command line the person ran starts with
bunorbunx;pnpm everyapp --versionfrom 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 installin 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 hintUpdate your lockfile using "pnpm install --no-frozen-lockfile". - Cause: pnpm ran frozen (
--frozen-lockfile, orCIset, which turns it on; agents often add the flag themselves) and what it computes foroverridesorsettingsfrom the checkout'spnpm-workspace.yamlandpackage.jsondiffers from what the lockfile recorded. What differed in the onboarding loop's run isn't confirmed: agit archiveof main installs frozen under pnpm 10.34.5 (the version the rootpackage.jsonpins inpackageManager), so a zip download is not the trigger by itself; a pnpm other than the pinned one is the usual cause. - Confirm:
pnpm --versionagainstpackageManagerin the rootpackage.json;env | grep ^CI=; whether the command line had--frozen-lockfile. - Fix:
pnpm installwithout--frozen-lockfileand withCIunset (env -u CI pnpm install); with an old pnpm,npm install -g pnpmfirst (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., fixin 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.tsandpackages/cli/package.json. - Confirm:
ls apps/every-app-gateway/scripts/build-release.ts packages/cli/package.jsonin the directory the person thinks is the checkout; the error'sThis copy is installed in <dir>names where the running everyapp lives. - Fix:
cdinto theevery-appclone and runpnpm everyapp …there. Source:packages/cli/src/lib/checkout.ts.
Not in the app directory
- Symptom:
everyapp dev,deploy,db,db restore(andcreate, after it wrote the app):MANIFEST_NOT_FOUND,No everyapp.config.ts in this directory, fixrun this from the app's directory(loadEveryAppManifest(cwd);devloads the manifest the same way, so it says the same).everyapp email …without--manifest: the same code with fixrun this from the app's directory, or pass --manifest <path>.No manifest at <path>is only whatemail --manifest <path>says when the file named isn't there (the default wording ofloadEveryAppManifestFile). A directory with a manifest but no Worker config:MISSING_CLOUDFLARE_CONFIG. Fromeveryapp dev, only when the manifest has nodevcommand:No cloudflare.config.ts in this directory,EveryApp apps build their Worker in cloudflare.config.ts with everyAppWorker() from @every-app/sdk/cloudflare.,data.fixcopy cloudflare.config.ts from starter. Fromeveryapp 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 thedefineConfig/everyAppWorker({ manifest, entrypoint, ctx })skeleton to copy andstarter has the full setup (package.json, vite.config.ts). - Cause:
deploy,dev,secret setof an app secret,email,dbrun in the app's directory (whereeveryapp.config.tsis: whateveryapp create <name>made, or a clone of the app);configure <app>,secret set --app <id>andsecret list <app>work from anywhere. - Confirm:
ls everyapp.config.ts cloudflare.config.tsin the current directory. - Fix:
cdinto the app's directory and run it again; for a missing Worker config, copy the skeleton from the message (orstarter/). 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, orCHECKOUT_MISSING. - Cause: npx fetches whatever
everyappis on npm, which is not what this repository runs (publishing is paused;packages/cli/src/lib/checkout.tsand.github/RELEASE.md), and that copy isn't inside a checkout. - Confirm:
pnpm everyapp --versionin the checkout againstnpx everyapp --version;which everyappor the shell history shows what was run. - Fix: always
pnpm everyapp …from theevery-appclone. Comparepnpm everyapp --versionwith 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:(orwas stopped by <signal>), the child's last lines indented,Full log: <path>, fixfix the build error above, then run this again.INSTALL_FAILED:Installing dependencies with pnpm failed(a manifest's owninstallcommand:Installing dependencies failed).GIT_FAILED:Initializing the app's git repository failed(create).COMMAND_FAILEDis the default code of a logged child that names no other. A manifest'sbuildcommand failing isBUILD_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 buildat the root, thenpnpm run build:releaseinapps/every-app-gateway; progressBuilding the Gateway from this checkout (usually 1–2m), stepgateway.release, log namegateway-build-<time>-<pid>.log). pnpm 10 and later block dependency build scripts unless approved, and pnpm 11 exits 1 withERR_PNPM_IGNORED_BUILDSin this non-interactive install; the Gateway release ships anallowBuildsallowlist in itspnpm-workspace.yamlfor that. - Confirm:
data.log(the full output),data.tail(the last 20 lines),data.exitCode,data.command; with--verbosethe 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 pullwithoutpnpm 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'spnpm run build:release) orBUILD_FAILED(an app'scf build) on Linux, the log ending inError: 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/hoststhat mapslocalhostto::1first (Debian bookworm does), the server binds IPv6 only and the prerender's client connects to127.0.0.1. everyapp now runs every build it spawns (the Gateway's,cf build,cf deploy,cf dev, a manifest'sbuildordevcommand) with--dns-result-order=ipv4firstappended toNODE_OPTIONS, keeping whatever was set. - Confirm:
getent hosts localhostprints::1;NODE_OPTIONS=--dns-result-order=ipv4first pnpm run build:releaseinapps/every-app-gatewaysucceeds. - Fix: update the checkout (
git pull,pnpm install) and run the same command; until then, run it withNODE_OPTIONS=--dns-result-order=ipv4firstset 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., fixadd --domain apps.<zone>, or another name under <zones>anddata.zones. With one active zone it carriesrerunpnpm everyapp setup --domain apps.<zone>(rerunNoteor another name under <zone>); with several,data.choices[]({ label: <zone>, rerun },choose_account's shape) and norerun: the agent runs one choice'srerun, or asks the person which (theinstall_gatewayrerunstatushands out ispnpm everyapp setupwith no--domain, so under--jsonthat rerun ends here).INVALID_DOMAIN:<input> isn't a full hostname, fixuse 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). WarningGATEWAY_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
--domainasks at a terminal and refuses otherwise (--json, CI, a pipe).--domainis 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, alwaysself_hostedfrom 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 --jsondata.installed,data.versionId,data.mode;cf zones list --account-id <id>for the active zones. - Fix: unattended, pass
--domain apps.<zone>(the error'srerun, or one ofdata.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_DIRECTORYbefore any command does anything, one of:everyapp is running as root (sudo) with your HOME,It would leave ~/.everyapp in <home> to root., fixrun 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>)., fixmake 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>)., fixmove 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 firstcloudflare.config.tsin its working directory or any parent, walks every ancestor of that directory: one another user can write to, or one holding acloudflare.config.ts, is refused. Running undersudowith the user'sHOMEwould leave~/.everyappto root. - Confirm:
id -u(0 under sudo) andecho $SUDO_UID;ls -ld ~ ~/.everyapp ~/.everyapp/cf;ls ~/cloudflare.config.ts /cloudflare.config.ts. - Fix: as the message says: no
sudo;chmod go-won the directory named; move the straycloudflare.config.tsinto 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.(orN hostnames already point somewhere elselisting each), withA wildcard record stays, and keeps answering every other name.when a wildcard answers it; fixdelete the record in Cloudflare (<zone> → DNS), or use another name;data.rerunis the same command with--force-dnsanddata.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, stepapp.domainorgateway.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 onregister_workers_subdomainafter it) leaves Cloudflare's placeholder record at the name,AAAA 100::; the check readscf workers listand, whenevery-app-gatewayholds the hostname, says so in aprogressline (<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 --jsonsays which Worker holds it (references.domains). - Fix: delete the record or pick another name (
--domainfor the Gateway; the app id decides an app's host).--force-dnspublishes over it (warningDNS_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., fixgive the API token Zone → DNS → Read, rerun with--force-dns(authorize). - Cause: listing the zone's records failed with cf's
authkind (API code 10000 or 9109): aCLOUDFLARE_API_TOKENwithout Zone DNS Read. - Confirm:
echo $CLOUDFLARE_API_TOKENis 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_PENDINGwarning saysIf 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 --jsondata.certificates[]keepsready: falsefor the host well past ten minutes; the zone's CAA records in the dashboard. - Fix: amend the CAA records, then wait;
statusreportsCERT_PENDINGuntil 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., fixdelete apps you no longer use (pnpm everyapp delete <app>), or install a second Gateway on another domain;data.total,data.limit. From 95 on, warningZONE_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) againstdata.limit(100);pnpm everyapp status --jsondata.liveDomainsanddata.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, aprogressline every 15 s:Waiting for a certificate: <hosts> (usually under five minutes)...), then warnedCERT_PENDING: setup'shttps://<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'sNo 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 --jsondata.certificates[]({ host, ready }, from the zone's certificate packs),data.tlsfor the Gateway host, theCERT_PENDINGfix (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 withURLandStatus: 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 stillbindingand goes active on its first request through the Gateway (503app_deployingwithretry-after: 5until then); or the hostname isn't one label below the Gateway's base domain (warningAPP_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 showsLive: Not routed). - Confirm:
status --json:data.certificates,data.apps[](status,bound),data.liveDomainsincludes 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., rerunpnpm db:generatethen run this again. OrSCHEMA_CHECK_FAILED:drizzle-kit couldn't compare the schema with the migrations,<why>. Nothing was migrated or uploaded., rerunpnpm db:generatein a terminal; it asks about renamed columns and tables. A warningSCHEMA_CHECK_SKIPPED(Can't check the schema against the migrations: drizzle-kit isn't installedorno drizzle.config.ts, withAn 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 generatewith the app's config butoutpointed at a copy of the migrations under.everyapp/schema-check-*; a new.sqlfile 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.schemastep under--verbose(The schema matches the migrations.when it passes;skipwith its reason when it didn't run); in the app's directory,pnpm exec drizzle-kit generate --config drizzle.config.tswrites a new.sqlfile under the migrations folder when there is drift (delete it, or keep it as the fix);git statusshows 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 existsTo roll the database back to before this run: pnpm everyapp db restore(db restore --gatewayfor 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, fixcheck POSTGRES_DATABASE_URL in .everyapp/secrets.env and the migrations;DRIZZLE_KIT_MISSINGwhen drizzle-kit isn't installed; for a Prisma app,PRISMA_MISSING(Prisma migrations need prisma, which isn't installed,Nothing was migrated or uploaded., fixadd prisma to devDependencies, then deploy again). A failure later in the run (the upload) after a migration applied appendsThe database stays migrated. To roll it back: …. - Cause:
cf d1 migrations apply(ordrizzle-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.migrationsstep'sdata(applied,fresh,restoreCommand,rollbackCommand). - Fix: correct the migration and deploy again; or
pnpm everyapp db restore(asks at a terminal,--yesotherwise, elseCONFIRMATION_REQUIRED;NO_RESTORE_POINTwhen 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, thenBuild it from everyAppWorker() in cloudflare.config.ts.AlsoThe app's Build Output isn't a production build. The manifest's build command must run cf build (production by default).andThe app's Build Output has no default Worker.Related:TIER_POLICY(<app> can't be deployed as configured, fixchange everyapp.config.ts, then deploy again),INSTALL_INPUTS_MISSING(The build binds <kind> <name>, which the deploy didn't provision, fixdeclare 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'sAppGatewayasEVERY_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 underresources); 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.buildstep (prepareAppBuildOutput, after the build and beforeapp.schema), so with--verbosethe run stops there; the lines under the headline are the whole list, one problem each. In the app's directory,cat cloudflare.config.tsshows whether the Worker comes fromeveryAppWorker(), and the Build Output under.cloudflare/output/v0/holds theworker.config.jsonthe list was read from. - Fix: build from
everyAppWorker(); declare every resource ineveryapp.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., fixdelete or rename it; for Hyperdrive,A Hyperdrive config named "<name>" already exists…, fixto 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, fixrename 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: rerunpnpm everyapp setupto upgrade the Gateway. - Cause: a deploy uses an existing resource only when
_everyapp_app_resourcesin the Gateway's D1 records it as the app's; a name proves nothing (anyone with the account can create one). Names areevery-<orgKey>-<appId>[--<binding>]. The warningRESOURCE_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 --jsondata.apps[]says whether this Gateway knows an app by that id; with--verbosetheapp.resourcesstep 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.tsrethrows aRegistrationErroras 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) andAPP_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_MISMATCHis still inregister()but nothing raises it: its only caller (registerFromManifest,packages/cli/src/commands/appGateway.ts) passes no expected Worker name (the removedeveryapp registerdid), and no command has a--workerflag. - 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 --jsondata.apps[]for this organization's apps; Admin → Apps for the organization; the error's own code and status. - Fix: another app id (
idineveryapp.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., fixset 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.variableslists them).pnpm everyapp statusshows the same line under the app'sAPP_NOT_UPLOADEDuntil 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.hyperdriveineveryapp.config.tsnames 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>, fixset POSTGRES_DATABASE_URL in .everyapp/secrets.env, then deploy again. - Cause:
migrations.dialectispostgresqland the secrets file has noPOSTGRES_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.envin the app's directory is 0;migrations.dialectineveryapp.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., fixgive the class a new name in everyapp.config.ts.HELPER_DURABLE_OBJECT_REMOVED:The helper Worker <w> still defines <Class>, fixkeep 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(orgit log -p -- everyapp.config.ts) in the app's directory shows thedurableObjectsentry removed, renamed or given anotherworkersince 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.tsin the app's directory shows theresources.workflowsbinding 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., fixdelete 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, fixshorten 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, rerunpnpm everyapp statusthen deploy again. Before that,progresslinesWaiting for <app> to go live(the hostname answered) orWaiting for the certificate for <host> (usually under 5m). The wait can also end without an error: warningACCESS_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.rerunpnpm everyapp status), warningCERT_PENDING, warningAPP_DISABLED(<app> is switched off in Admin → Apps), orAPP_NOT_FOUND(<app> was deleted during this run). Behind Access the wait'sprogressline isCloudflare Access answers before the Gateway; waiting for the Gateway to make <app> live (it looks every minute). - Cause: the app's registry row says
bindinguntil the Gateway marks itactive, which it does on the first request that reaches the app over the service binding on a Gateway version that has it (probeAppfetcheshttps://<host>/__everyapp/healthover the binding). The wait sendsGET 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 everybindingapp of the install the same way (activateDeployingApps), as do the Gateway's home page and Admin → Apps for eachbindingapp 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 stillDeployingafter 10 minutes readsDeploy stalledand says to runstatus, then deploy again, which is what the CLI'sACCESS_IN_FRONTsays at the same point. The Gateway answered but never activated the app: itsGATEWAY_DEPLOYMENT_MODEis unset or invalid (bindingActivationoff: 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'sstatusandbound;data.mode;data.versionIdagainstdata.recordedVersionId;data.inSync; the fixes. - Fix: run what
statussays (pnpm everyapp setuprebinds 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 createdorDatabase up to date) andUploadedunder it (stepgateway.upgrade), and takes minutes longer; orstatussaysGATEWAY_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.; runpnpm everyapp update, noteupdates it now; severityinfo, listed after the problems to fix). Related refusals:CLI_TOO_OLD(Gateway <v> needs everyapp <minCli> or newer (this is <v>), fixupdate everyapp (in a checkout: git pull), then run this again),RELEASE_UNSUPPORTED(Gateway release <v> needs a newer everyapp), andDOWNGRADE_REFUSEDwhen 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
gatewaylease). A code-only redeploy of an app the Gateway already binds never touches the Gateway.upToDatecompares the checkout's tree hash with the install row's. - Confirm:
status --jsondata.upToDate,data.checkout,data.release. - Fix: nothing, when the upgrade went through;
pnpm everyapp setupupgrades on its own; a failed upgrade after its migration says how todb 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., fixclone 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 malformedrelease.json, a manifest this everyapp refuses, an app id that isn't the manifest's, migrations outsidemigrations/.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 iseveryapp-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 butrelease.json. - Confirm:
curl -sI https://github.com/<owner>/<repo>/releases/latest/download/everyapp-release.tar.gzanswers 302 (to the tag's asset) when a public release has the asset and 404 otherwise (a private repository answers 404 too);data.expectedagainstdata.actualfor 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/stateor.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., fixbuild with cf build (production by default);Expected the Build Output v0 alone in <dir>/.cloudflare/output, found <entries|none>; ortar 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/installanswers{ "available": false }. A self-hosted Gateway installs only withBROWSER_APP_INSTALLSexactlyon(off by default) besideCF_DEPLOY_TOKENandCLOUDFLARE_ACCOUNT_ID, for the install organization's owners and admins from a session under 10 minutes old (APP_INSTALL_REAUTH_REQUIREDotherwise); it used to install whenCF_DEPLOY_TOKENandCLOUDFLARE_ACCOUNT_IDwere set on the Gateway Worker,APP_BASE_DOMAINis (setup writes it) andBROWSER_APP_INSTALLSisn'toff(selfHostedInstallEnabled,browser-install.ts); a hosted one while its launch gate ison. Setup asks for the token once, at a terminal, when the Worker has noCF_DEPLOY_TOKEN(--yesanswers 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_INSTALLSwithoffturns installs off. On a self-hosted Gateway an installed app getsBROWSER_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 runningpnpm install && pnpm everyapp deployin 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 whatpnpm everyapp releasepacks (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: changeeveryapp.config.tsas 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 haveanda Durable Object class names a helper Workerfrom the manifest check (apps/every-app-gateway/src/server/deploy/metadata.ts), and for a release's Build Outputits 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 thedeployscope and nothing else (*doesn't count), made in Admin → Tokens (kindDeploy 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 expiredepat_token, one whose owner left or was deactivated, or a deploy key (itsdeployscope opens no app). 401 withreason: "duplicate_session_cookie": theCookieheader names the session cookie twice (cookie tossing); the browser must drop the extra cookie. 403forbiddenwithreason: "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. 403forbiddenwithreason: "token_scope": an MCP-only token (allmcp:scopes) on any route but/mcp, or on any route of a host whose manifest has nomcp(the default: such a host serves no MCP). 403csrf_deniedand 403server_fn_denied: next entry.packages/perimeter/src/gateway.ts,apps/every-app-gateway/src/perimeter/betterAuthAuthenticator.ts. - From the SDK, 401
unauthenticatedwithmessage: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 matchEVERYAPP_IDENTITY_PUBLIC_KEYS: after a Gateway key rotation every app must be redeployed,docs/runbooks/key-rotation.md;audmust equalEVERYAPP_AUDIENCE, the Worker's name;issmust equalEVERYAPP_IDENTITY_ISSUER, the Gateway URL),EVERYAPP_IDENTITY_ISSUER not configured — deploy with the EveryApp CLI or provide an explicit issuer.,unexpected token typ. 500misconfigured: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)., orDev identities are accepted only from the dev issuer https://gateway.dev.localhost, not "…". EVERYAPP_DEV is for everyapp dev alone.(EVERYAPP_DEVreached 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'serrorandreason/message. A deploy stopping withIDENTITY_KEY_UNKNOWN(The Gateway is missing its sign-in public key) means the install row has nojwt_public_key: step 3 of the key rotation runbook. - Fix: grant the app; sign in; redeploy the app after a key rotation (
pnpm everyapp deployfrom each app's directory); build witheveryAppWorker(). Identity, headers and the SDK, Key rotation.
Gateway keys rotated or lost
- Symptom: every app answers 401
unauthenticatedwithidentity verification failed: …after the Gateway'sJWT_PRIVATE_KEY/JWT_PUBLIC_KEYchanged; or a deploy stops withIDENTITY_KEY_UNKNOWN:The Gateway is missing its sign-in public key,App Workers need it to check who is signed in.,data.fixrestore 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_KEYSat deploy time, read from the install row (_everyapp_install.jwt_public_keyin 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 whatIDENTITY_KEY_UNKNOWNreads. - Confirm:
pnpm everyapp statuslists the apps; an app's 401 body saysidentity 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 withnode apps/every-app-gateway/regenerate-secrets.js(a PKCS#8 private key and an SPKI public key, RS256, 2048-bit; an OpenSSH or PKCS#1BEGIN RSA PRIVATE KEYkey deploys without error and fails at runtime) and replace bothJWT_PRIVATE_KEYandJWT_PUBLIC_KEYon 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 privatekey.json(the PEM's line breaks as\n), thenCLOUDFLARE_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-gatewaygives the id); deletekey.jsonafter. For a lost key (IDENTITY_KEY_UNKNOWN), this step alone, with the Gateway's currentJWT_PUBLIC_KEY. 4.pnpm everyapp setup --redeployat a terminal and confirm cf's prompt (the Worker changed outside everyapp, so an unattended run would stop withrerun_interactive;--redeployisUpload the Gateway even when it is up to date (after a change in the dashboard, such as a key rotation), a hidden flag ofsetup). 5.pnpm everyapp deployfrom each app's directory. Then verify a private route answers 401 without a session and 200 with one, and a forgedx-everyapp-identityheader 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
publicineveryapp.config.tslists them;methodsdefaults toGETonly; 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,:nameone 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 fromdocs/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/callis 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 withusernull andx-everyapp-publicset. 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 anOriginequal 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 issame-site, notsame-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 sendOriginequal to the app's host (websocket upgrade without origin,websocket origin <x> != <host>). TanStack Start server functions under/_serverFn(or the manifest'sserverFnBase) answer only same-originfetch()calls carryingx-tsr-serverFn: trueorx-tsr-redirect: manual: reasonsserver function navigation,sec-fetch-site <x>,missing server function client header.serverFnGuard: falsein the manifest turns that guard off. - Confirm: the
reasonin the body; the request'sOrigin,Sec-Fetch-Site,Sec-Fetch-Modeandx-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; setserverFnBasewhen 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_GATEWAYbinding (createGatewayFetch(),integrationFetch(),sendEmail(),listMembers()) fails; the response carriesx-everyapp-error: <code>and a body{ error, code, message }(AI calls: OpenAI's error shape withtype: "gateway_refusal", pluscodeandmessageat the top level), which the SDK'sgatewayRefusal()/GatewayRefusalErrorreads. - 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'sai.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., orAn 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 validscopeslist, so the whole grant is void),integration_grant_not_allowed(403: agrant_typethat 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 fromintegrationStatus()'s scope sets orauthorizeUrl(); to use more, add the set to the entry'sscopesineveryapp.config.tsand 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 stillpendingorbinding, ordisabled, calling AI, email or members; see App states, checkstatus --jsondata.apps[].statusand open the app's URL once; alsoShared 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)ornot set (one of: <group>), and under--jsondata.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_TOKENwithCLOUDFLARE_ACCOUNT_ID, …; live at once, no deploy), orpnpm everyapp configure <app>for everything the app needs; a missing capability goes ineveryapp.config.tsand needs a deploy. Ineveryapp devthere 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(indexsetup/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): addcapabilities: { 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'sfromisn'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 refusesEMAIL_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 withretry-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 ownemail_send_failed(The EveryApp gateway answered <status> to the send.) is raised bysendEmail()when the response is neither a refusal nor ok (packages/sdk/src/server/sendEmail.ts). Every send logsemail.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 ofemail set, before any manifest or network:EMAIL_NOTHING_TO_SET(Name what to set: --sender, --inbound, or both),EMAIL_SENDER_INVALIDandEMAIL_INBOUND_INVALID(<N> problems with --sender|--inboundwith one• <problem>line each anddata.problems, or the single problem as the headline). - Receiving: the manifest needs
email: { inbound: true }and the Worker anemail()export; the addresses are the installation's (pnpm everyapp email set --inbound <address>;*@<apex>is the zone's catch-all).pnpm everyapp email showprints each address asrouted,not routed yet(deploy routes it),not declared(the manifest doesn't declare inbound:pnpm everyapp email unset inboundclears it) orremoved, still routed.statusreportsEMAIL_INBOUND_UNDECLARED,EMAIL_ROUTE_PENDINGandEMAIL_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 deployfrom 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.commandsthenpx cf email-routing enable …lines),EMAIL_ADDRESS_TAKEN(<address> already goes to another app (<app>)oralready <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, orpnpm 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 statuspending: registered, but no deploy of it finished (its Worker isn't uploaded).statussaysAPP_NOT_UPLOADED. Fix:pnpm everyapp deployin the app's directory (orpnpm everyapp delete <app>). Requests to its host answer 503{ "error": "app_deploying", "status": "pending" }.Deploying: statusbinding: the Worker is uploaded and the row waits for a Gateway version that binds it to serve a request; that request makes itactive(the deploy's live wait sends it). Requests answer 503app_deployingwithretry-after: 5.statussaysAPP_BINDINGwhen the live Gateway binds it (it goes live on its first visit: open the URL) andBINDINGS_DIFFERwhen it doesn't (pnpm everyapp setup). It staysDeployingfor good when the Gateway'sGATEWAY_DEPLOYMENT_MODEis unset or invalid (activation is off: App not live after deploy).Disabled: statusdisabled: an admin switched it off in Admin → Apps. Requests answer 503{ "error": "app_unavailable", "status": "disabled" }; a deploy warnsAPP_DISABLEDand leaves it uploaded. Fix: turn it on there.Needs setup: statusactive, but a required setting of its manifest (a provider's key, a capability's client, one of its ownsecrets;requiredunless markedoptional: trueoranyOf) isn't set. A private HTML GET navigation gets a 302 tohttps://<gateway>/?setup=<app>(the launcher's setup dialog); everything else, public routes included, gets 503{ "error": "app_needs_setup", "app": "<app>" }with noretry-afterand never a setting's name; its tools stay out of/mcp.statussaysAPP_NEEDS_SETUP(<app> needs setup) with the one command,pnpm everyapp configure <app>, and the deploy that uploaded it warnedAPP_NEEDS_SETUP(<app> won't open until <NAMES> is set) with the same command innext. 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 pipesNAME=valuelines 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
MCPchip beside the name: an MCP app (tierprovider, 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/mcpaddress,<id>_<tool>) rather than a page.Activeonce its key or credential is set;Needs setupwhile 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 setagain pins it here);Offaftermcp off(not served, everyone's access kept;mcp onserves it again);Not supported yetfor a server that wants OAuth.mcp removeleaves a tombstone, so the app is gone from the launcher and Admin → Apps (before 2026-10-25mcp offdid that). A remote app with no tools listed:pnpm everyapp mcp show <id>gives the last listing's error, andmcp refresh <id>lists again. MCP. - Other answers on an app host: 503
app_unreachable(the Gateway has no working binding to the Worker:statussaysAPP_WORKER_MISSINGorAPP_NOT_BOUND), 502app_error(the app's Worker threw: its logs, see When to escalate), 503app_unavailablewithapp(a row with a tier or scope this Gateway doesn't know), 404no_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), sorevocation/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'sbannedflag 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 secretswithDid 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, fixcd 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.), fixadd it to secrets in everyapp.config.ts: secrets: [...](with--app,… and deploy <id> again), rerunpnpm everyapp secret set <name>[ --app <id>]. Fromconfigure <app>withNAME=valuelines 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); thenThe 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), fixleave 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'tNAME=valueat all isPIPED_LINE_INVALID(Line <n> of the piped input isn't NAME=value,Nothing was set., fixpipe 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 whoseeveryapp.config.tsis in the current directory; either manifest must list it undersecretsforset. Refused before any value is read. - Confirm:
pnpm everyapp secret list <app>(orpnpm everyapp secret listin 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.didYouMeanwhen 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., rerunpnpm everyapp deploy). Before that deploy,secret listin the app's directory shows each declared secret asafter first deploywith<app> isn't deployed yet; its own secrets can be set after that.andpnpm everyapp deploylast indata.next(data.app.deployed: false).data.nextis a list:pnpm everyapp configure <app>first, while something the app needs isn't set, thenpnpm everyapp deployfor 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 nodata.next;secret setof an app secret failsAPP_NOT_FOUNDuntil the first deploy, and so does a piped one inconfigure(<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, rerunpnpm everyapp status; a row whose first deploy never finished,status: "pending", counts as undeployed). Adding a name tosecretsneeds no deploy beforesecret setorconfigurein 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>., fixpick 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 forTHIRD_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., fixset it in the Gateway's .dev.vars(MCP_ACCESS_TOKEN_TTL,DATAFORSEO_SANDBOX). A manifest that lists a Gateway setting undersecretsfails 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 --jsoncarries every settable Gateway setting indata.gateway.settable(the table shows what is set, what setup wrote and, of the rest, only what some app here reads, thenN more can be set), so a name not there and not under the app'ssecretsis neither;grep -n '<name>' everyapp.config.ts cloudflare.config.tsshows 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 listshows it in an unexpected place. - Cause: the routing is by name only. A Gateway setting (
OPENAI_API_KEY,EMAIL_FROM, theMCP_*andCLOUDFLARE_ACCESS_*names, …) lives on the Gateway Worker for every app and is reached through the Gateway's capabilities, never asenv.NAMEin the app; an app secret lives on the app's Worker (and its helpers) asenv.NAME. Both change the live Worker at once (Live now. No deploy needed.) and survive deploys. With--verbose,secrets.routeprogress 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; columnsSETTING,WHERE,STATUS: the app's own secrets and the Gateway settings its manifest needs); outside an app with no id, the Gateway's (SET BYyou,setupornot set), then every app's settings.--jsoncarriesdata.app.workers[].names,data.app.settings,data.app.setupanddata.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--appand one of the app's own is left); throughconfigure, the rerun ispnpm everyapp configure <app>(to set what is left; before the first deploypnpm 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 devruns the app without a secret that production has, orsecret setunder--jsonfails withSECRET_VALUE_REQUIRED(Can't ask for <name>'s value without a terminal,Or pipe it in: echo "$NAME" | pnpm everyapp secret set NAME, rerunpnpm everyapp secret set <name> --from-env <name>); orconfigure <app>under--jsonwith nothing piped and no--from-envvariable 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.namesthe names still missing; rerunpnpm everyapp configure <app> --from-envwithrerunNotewith the variables exportedonce deployed, before the first deploypnpm everyapp configure --from-envwithin the app's directory, with the variables exported). - Cause: production values never come down; in development the Worker reads
.dev.varsin 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) andSECRET_SEVERAL_NAMES(one value sets one name) come from that.configure <app>readsNAME=valuelines from stdin (one per line; blank lines and#lines skipped, anexportprefix 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'tNAME=valueisPIPED_LINE_INVALID(Line <n> of the piped input isn't NAME=value; never echoed, since it may be a bare secret, which goes topnpm everyapp secret set NAME), a blank piped value isSECRET_EMPTY(<name> is empty in the piped lines), and a value typed after the verb (configure NAME=value) isVALUE_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 asTOO_MANY_ARGUMENTSwith the input cut at its=("OPENAI_API_KEY=…"), and--from-env=<anything>asINVALID_VALUE(--from-env is a switch and takes no value), both with the lineA 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.varsfor dev; for an agent,export NAME=…thenpnpm everyapp configure <app> --from-env --json(every exported name at once) orpnpm 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.1at--port, else$PORT, else8787(--hostfor 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 (anothereveryapp dev, aviteorcf devleft running);data.freeis the next free port. - Fix: the rerun with
--port, or stop the othereveryapp 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'sdevcommand, with the Vite and workerd processes they started), emits the stepdev.stopand exits 130, after whichpnpmprints itsELIFECYCLEline for exit code 130: that line is pnpm's, not a failure of dev's. Don'tpkill -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, warningsMIRROR_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, orCan't reach the local Gateway at <url>withIs pnpm dev running in apps/every-app-gateway?.INVALID_DEV_MODE:--mode must be stub or mirror, not "…"(orEVERYAPP_DEV_MODE). - Cause:
--mode mirrorresolves 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_MODEwhen nobody passed--mode. - Fix:
pnpm devinapps/every-app-gatewayfirst, or--gateway <url>; or stay instubmode (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.fixnames the local Gateway'sEVERYAPP_DEV_COOKIE_DOMAIN=everyapp.localhostandGATEWAY_URL=http://everyapp.localhost:<port>. A 421This dev server serves <app>.localhost, not <host>.with the warningDEV_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:8787and the bare base host is the launcher. Mirror mode needs a cookie scoped to a parent domain, impossible on single-labellocalhost. Listening on anything but loopback warnsDEV_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,MCPandLog inURLs 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 useeveryapp.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.OrDEV_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.fixmake its dev command (<dev>) listen on the port in PORT(a manifest with adevcommand) orin vite.config.ts, set server.port: Number(process.env.PORT)(a manifest without adevcommand). - Cause: everyapp starts the app's dev server with
PORTset to a port it picked and waits forhttp://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 whenvite.config.tshardcodesserver.portor thedevcommand passes--port. - Confirm: the server's output above the error;
grep -n 'port' vite.config.tsand the manifest'sdevcommand;lsof -nP -iTCP -sTCP:LISTEN | grep nodewhile it runs shows which port it took. - Fix: for a crash, fix what its output says and run
pnpm everyapp devagain; for the port,server.port: Number(process.env.PORT)invite.config.ts, or make thedevcommand 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
postinstallispnpm -C packages/sdk build(package.json), so apnpm installanywhere in the checkout rewritespackages/sdk/dist; a deploy does the same, through itsapp.installstep (the package manager's install in the app's directory) and, when it rebinds the Gateway, the release build from the checkout, which starts withpnpm --filter @every-app/sdk build(packages/cli/src/lib/gateway/checkoutRelease.ts). The dev server resolves@every-app/sdkfrom thatdist, 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.fixcorrect the migration, then run <command> again. Dev doesn't start. - Cause: before the app starts,
everyapp devapplies the app's pending migrations to the local databasecf devserves (stepdev.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 devagain;--skip-migrationsstarts 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 whileeveryapp devruns, but Claude Code still lists the old tools athttp://<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 declareslistChanged: falseand 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 andclaude mcp add --transport http <name> http://<app-id>.localhost:8787/mcpagain). On a deployed Gateway, connected 2026-era clients getnotifications/tools/list_changedwithin 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_ENABLEDisfalse,0,off,no, or anything else unrecognized, which logsmcp.oauth.flag_unrecognized):/oauth/*and the discovery documents answer 404 and/mcpanswers 401 withWWW-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 noregistration_endpointand/oauth/registeranswers 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; logmcp.oauth.registration_limited). A browser origin isn't allowed: a page on another origin needs it inMCP_ALLOWED_ORIGINS(http://localhost:6274for MCP Inspector's direct mode). The resource doesn't match: the protected resource metadata'sresourcemust equal exactly what the user typed,https://<gateway>/mcp, soGATEWAY_URLmust have no path; a refresh with anotherresourceanswersinvalid_targetand 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(expectBearer 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(resourceexactlyhttps://<gateway>/mcp);curl -s https://<gateway>/.well-known/oauth-authorization-server(issuer,client_id_metadata_document_supported); Worker logsmcp.oauth.error. A browser-based client that gets a 403 JSON-RPC errorForbidden: origin not allowed.(code-32000) sent anOriginthat is neither the Gateway's own nor inMCP_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 devalso answersForbidden: invalid Host header.to a non-localhost Host). - Fix:
pnpm everyapp secret set MCP_OAUTH_ENABLED/MCP_DCR_ENABLEDwithtrue; setMCP_ALLOWED_ORIGINS; thenclaude mcp add --transport http everyapp https://<gateway>/mcpand 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 403token_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 aprovider:scope; or it is a deploy key (deployscope: a token for the Gateway's deploy API, made in Admin → Tokens; it opens no app). 403no_app_access: a token bound to one app used on another's host, or the member lacks the app. 403token_scope: a token whose scopes are allmcp:scopes on any route but/mcp, on every route of a host whose manifest has nomcp, or on/mcpa scope other than*,mcp:writeormcp:read. An app host serves/mcponly when its manifest saysmcp: { by, why }(Manifest); without it the path is the app's own private route (usually 404), and a client pointed athttps://<app-host>/mcpmust connecthttps://<gateway>/mcpinstead. Where it is served, it takes personal tokens and the app's own session only, never an OAuth token (its 401 saysUse 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
errorandreason; the token under Admin → Tokens (kind, scopes, bound app). - Fix: a new token with the right scopes, or the Gateway's
/mcpwith 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 answersUnknown tool; an app's tools are absent altogether. - Cause: tools with
risk: "destructive"are hidden from every caller until approvals exist (reasondestructive_requires_approval;TODO.md, "Destructive tools"); write tools (a tool without a risk counts as write) are hidden from read-only connections andmcp:readtokens (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 andUnknown toolon a call. The Gateway's/mcpserves 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, andeveryapp_list_appsnames it withneeds setup: an admin runs pnpm everyapp configure <app>; an MCP app that is on (pnpm everyapp mcp on dataforseo) adds itsdataforseo_<tool>tools onceDATAFORSEO_API_KEYis 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 nodefineTools()catalog has none. - Confirm: call
everyapp_list_appsandeveryapp_whoami; check the tool'sriskin the app'stools.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 readWWW-Authenticate: one namingcloudflare-access-protected-resourceis Access's (the person isn't allowed by the policy or identity provider); one namingoauth-protected-resource(orrealm="every-app"with no metadata) is the Gateway's: it didn't take the assertion; logsmcp.access_assertion_ignoredwith reasoninvalid(wrongCLOUDFLARE_ACCESS_AUDor team domain, oraccess.jwks_fetch_failed) oraudience(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'tself_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 theCF-Access-Client-Id/CF-Access-Client-Secretheaders 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., orYou'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 withCLOUDFLARE_ACCESS_TEAM_DOMAINandCLOUDFLARE_ACCESS_AUDboth 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 aCLOUDFLARE_ACCESS_AUDthat isn't an AUD tag leaves the ring off and logsaccess.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) anddata.settings.current(falsewhen the settings name an application that no longer exists: the human output saysname an application that no longer exists: nobody can sign in),data.applications.gateway/data.applications.appswith theirinSyncandpolicyAttached;pnpm everyapp secret listfor the names only (values can't be read back). - Fix:
pnpm everyapp access offremovesCLOUDFLARE_ACCESS_AUD,CLOUDFLARE_ACCESS_APPS_AUDandCLOUDFLARE_ACCESS_TEAM_DOMAINin 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,--yesunattended:Turning Access off needs confirmation at a terminal, or --yes).pnpm everyapp access off --keep-applicationsremoves 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_AUDis the one-setting form (the ring needs bothCLOUDFLARE_ACCESS_TEAM_DOMAINandCLOUDFLARE_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 laterpnpm everyapp setupat a terminal (it asks before replacing a version it didn't upload:rerun_interactive). Thenpnpm everyapp access onputs the ring back once the cause is fixed. For the wrong person:Not you? Sign out of Accesson 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 onadds 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 lastaccess 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 inCLOUDFLARE_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 onadds 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.notYetnames 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.appsis whatonwould write) and carry the policy (policyAttached), the sign-in methods, anddata.settings.current(falsewhen the settings name applications that no longer exist);data.fixesis["pnpm everyapp access on"]whenever any of those is off, and absent otherwise;data.linkPendingnames 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 showsays when they are stale, and--write-settingsrewrites them regardless. For people without an invitation, add their domain toCLOUDFLARE_ACCESS_AUTO_JOIN_DOMAINS(exact match;acme.comdoesn't covereng.acme.com;access on --email-domain <domain>lets them through Access, and the command's output names this setting) or setCLOUDFLARE_ACCESS_OUTER_RING_AUTO_JOIN=everyoneon 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.onContinue with Company SSO;No "Continue with" button; a member can't sign in afterEMAIL_PASSWORD_SIGN_INwas set tofalse. - 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_problemin the Worker's logs).EMAIL_PASSWORD_SIGN_IN=falsetakes 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 istrueagain. - Confirm:
pnpm everyapp access show --jsondata.linkPendingnames the members who have not connected an Access identity yet (access onprints the same list as who signs in once with their password,data.signIn.pending;readPeopleinpackages/cli/src/commands/access/index.ts: accounts with acredentialrow and nocloudflare-accessrow);pnpm everyapp secret listshows whetherEMAIL_PASSWORD_SIGN_INand theCLOUDFLARE_ACCESS_*/ OIDC settings are set; the Worker's logs foraccess.config_problem. - Fix: have everyone sign in with Access once before turning passwords off;
pnpm everyapp secret set EMAIL_PASSWORD_SIGN_INwithtrueto 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 forgedx-everyapp-identityheader 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/callgets 404;curl -sI https://<host>/shows HSTS andnosniffon any answer; the CSP withframe-ancestors 'none'is on HTML only, so look for it on a page an anonymous caller can render (a declared public route, orhttps://<gateway-host>/sign-in): a private route's anonymous401JSON and302carry none.Referrer-Policyisstrict-origin-when-cross-originon app hosts andno-referreron the control plane. - Registry:
pnpm everyapp status --jsondata.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, thennpx cf workers deployments create --worker every-app-gateway --strategy percentage --versions '[{"version_id":"<VERSION_ID>","percentage":100}]'.pnpm everyapp db restore --gatewaytakes the Gateway's database back to the restore point saved before its last migration (--jsongives cf's owntime-travel restorecommand); 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 withrerun_interactive. For a lasting rollback, check out the good commit and runpnpm 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--jsonfor 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--verboseit suggests--verbosefor a stack trace to report; under--jsonthe stack is indata.stackeither 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(cfhas no tail; wrangler asks for its own one-time login). Step by step in the skill'ssetup/gateway.md, "Verify". - Bring it to the Discord (the user docs' channel). Include: the exact command line,
pnpm everyapp --version, theerrororaction_requiredevent (code, message,data) or the--verboseoutput,pnpm everyapp status --json(itsdata.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.mdxlanding-page/content/docs/people.mdxlanding-page/content/docs/gateway.mdxpackages/cli/src/commands/status/problems.tspackages/cli/src/commands/status/index.tspackages/cli/src/lib/errors.tspackages/cli/src/lib/output/events.tspackages/cli/src/lib/output/report.tspackages/cli/src/lib/output/run.tspackages/cli/src/lib/output/prompt.tspackages/cli/src/lib/childLog.tspackages/cli/src/lib/cf/account.tspackages/cli/src/lib/cf/deploy.tspackages/cli/src/lib/cf/errors.tspackages/cli/src/lib/cf/plan.tspackages/cli/src/lib/cf/runner.tspackages/cli/src/lib/cf/toolchain.tspackages/cli/src/lib/cf/d1.tspackages/cli/src/lib/runtime.tspackages/cli/src/lib/checkout.tspackages/cli/src/lib/manifest.tspackages/cli/src/bin/cli.tspackages/cli/src/lib/domains/preflight.tspackages/cli/src/lib/domains/zones.tspackages/cli/src/lib/domains/certificates.tspackages/cli/src/lib/domains/certWait.tspackages/cli/src/lib/domains/localDns.tspackages/cli/src/lib/domains/https.tspackages/cli/src/lib/gateway/lease.tspackages/cli/src/lib/gateway/apply.tspackages/cli/src/lib/gateway/discover.tspackages/cli/src/lib/gateway/connect.tspackages/cli/src/lib/gateway/install.tspackages/cli/src/lib/gateway/reconcile.tspackages/cli/src/lib/gateway/release.tspackages/cli/src/lib/gateway/setup-link.tspackages/cli/src/lib/gateway/secretsFile.tspackages/cli/src/lib/gateway/registry.tspackages/cli/src/commands/setup/runSetup.tspackages/cli/src/commands/setup/deployGateway.tspackages/cli/src/commands/appGateway.tspackages/cli/src/commands/deploy/index.tspackages/cli/src/commands/deploy/deployApp.tspackages/cli/src/commands/deploy/steps/checkSchemaDrift.tspackages/cli/src/commands/deploy/steps/waitForLive.tspackages/cli/src/commands/deploy/steps/offerGateway.tspackages/cli/src/commands/dev/index.tspackages/cli/src/commands/dev/mode.tspackages/cli/src/commands/dev/gatewayServer.tspackages/cli/src/commands/dev/remoteAuth.tspackages/cli/src/commands/dev/migrations.tspackages/cli/src/commands/dev/ready.tspackages/cli/src/commands/dev/command.tspackages/cli/src/commands/dev/proxy.tspackages/cli/src/commands/secret/index.tspackages/cli/src/commands/email/index.tspackages/cli/src/commands/db/restore.tspackages/cli/src/commands/db/index.tspackages/cli/src/commands/create/index.tspackages/cli/src/commands/setup/command.tspackages/cli/src/commands/access/index.tspackages/cli/src/commands/access/command.tspackages/cli/src/lib/access/plan.tspackages/cli/src/lib/access/record.tspackages/cli/src/lib/access/sync.tspackages/cli/src/lib/cf/access.tspackages/cli/src/lib/interrupt.tspackages/cli/src/lib/longRunning.tspackages/cli/src/commands/dev/appServer.tspackage.jsonpackages/cli/src/lib/app/cloudflareConfig.tspackages/cli/src/lib/app/workflows.tspackages/cli/src/lib/app/buildOutput.tspackages/cli/src/lib/gateway/checkoutRelease.tspackages/cli/src/lib/package-manager.tspackages/cli/src/lib/git.tspackages/cli/src/lib/domains/hostnames.tspackages/cli/src/lib/cf/privateDir.tspackages/cli/src/lib/migrations/prisma.tspackages/perimeter/src/registry/registration.tspackages/perimeter/src/manifest/deploy.tsapps/every-app-gateway/src/server/deploy/metadata.tsapps/every-app-gateway/src/server/deploy/release.tsapps/every-app-gateway/src/server/deploy/bundle.tsapps/every-app-gateway/src/server/deploy/cfRestDeployer.tsapps/every-app-gateway/src/server/deploy/pipeline.tsapps/every-app-gateway/src/server/deploy/run.tsapps/every-app-gateway/src/server/deploy/registry.tsapps/every-app-gateway/src/server/deploy/browser-install.tsapps/every-app-gateway/src/server/deploy/install.tsapps/every-app-gateway/src/server/repositories/DeployJobRepository.tsapps/every-app-gateway/src/server/capabilities/members.tsapps/every-app-gateway/src/server/capabilities/oauthClient.tsapps/every-app-gateway/src/server/toolbus/gateway-mcp.tspackages/cli/src/lib/app/policy.tspackages/cli/src/lib/app/installBuildOutput.tspackages/cli/src/lib/app/resources.tspackages/cli/src/lib/app/ownership.tspackages/cli/src/lib/app/deploySecrets.tspackages/cli/src/lib/app/buildApp.tspackages/cli/src/lib/app/release.tspackages/cli/src/lib/app/secrets.tspackages/cli/src/lib/app/emailRoutes.tspackages/cli/src/lib/secretValue.tspackages/cli/src/lib/migrations/remote.tspackages/cli/src/lib/migrations/postgres.tspackages/cli/src/lib/release/github.tspackages/cli/src/lib/release/fetch.tspackages/perimeter/src/gateway.tspackages/perimeter/src/csrf.tspackages/perimeter/src/serverFunctions.tspackages/perimeter/src/publicRoutes.tspackages/perimeter/src/registry/apps.tspackages/perimeter/src/manifest/manifest.tspackages/sdk/src/internal/identity.tspackages/sdk/src/internal/protocol.tspackages/sdk/src/server/everyApp.tspackages/sdk/src/server/sendEmail.tspackages/toolbus/src/policy.tspackages/toolbus/src/http.tsapps/every-app-gateway/src/server/app-activation.tsapps/every-app-gateway/src/server/platform-policy.tsapps/every-app-gateway/src/server/app-gateway-entrypoint.tsapps/every-app-gateway/src/server/ai-proxy.tsapps/every-app-gateway/src/server/capabilities/refusal.tsapps/every-app-gateway/src/server/capabilities/email.tsapps/every-app-gateway/src/server/organization/orgContext.tsapps/every-app-gateway/src/server/deploy/auth.tsapps/every-app-gateway/src/server/deploy/errors.tsapps/every-app-gateway/src/server/deploy/routes.tsapps/every-app-gateway/src/perimeter/betterAuthAuthenticator.tsapps/every-app-gateway/src/client/lib/appStatus.tsapps/every-app-gateway/src/auth/shared.tsapps/every-app-gateway/src/auth/cloudflare-access/config.tsapps/every-app-gateway/src/auth/cloudflare-access/membership.tsapps/every-app-gateway/src/auth/cloudflare-access/plugin.tsapps/every-app-gateway/src/client/sign-in-methods.tsdocs/runbooks/key-rotation.mddocs/runbooks/cloudflare-access.mddocs/runbooks/mcp-oauth.mddocs/runbooks/email-sending-setup.mddocs/runbooks/hosted-install.mddocs/TESTING.mddocs/security-model.mddocs/capabilities.mdTODO.md
Error codes
Every code an everyapp command can end with (errors, the reporter's and cf's, argument errors), the codes the Gateway's deploy API and browser install answer, every warning, and every problem status reports, each with what it means and how to fix it.
everyapp status: the report and its fixes
Drive `pnpm everyapp status --json` to learn what is left to do on a Gateway and its apps, read the report line by line against `done.data`, and apply each fix in order until `fixes` is empty.