EveryAppEveryApp

The --json protocol

What an everyapp command writes on stdout with --json (NDJSON events), how the run ends (exit codes 0, 1, 2, 130, 251, 252), and what an agent does with each event, action and error.

Every pnpm everyapp command that does work takes --json, except dev (long-running and for a person; dev --json is UNKNOWN_FLAG, exit 252). With it, stdout carries a stream of events an agent reads line by line, nothing prompts, and the exit code says how the run ended. This page is the protocol; the commands are on /docs/agent/cli, the error codes on /docs/agent/errors, and the handoffs setup makes on /docs/agent/setup. The source of truth is the header comment of packages/cli/src/lib/output/events.ts.

The stream

  • One JSON object per line (NDJSON) on stdout. Every event is { "v": 1, "ts": "<ISO 8601>", "type": "<type>", … }: v is the protocol version, ts is new Date().toISOString() at emit time (createNdjsonSink in packages/cli/src/lib/output/events.ts).
  • Nothing else lands on stdout. The lines a person would see are a filter over the same events (createHumanSink), which --json replaces; done's display (the table or lines drawn for a person) is set to undefined before the line is serialized, so it is absent in --json; a script reads data instead.
  • --json is read before any command parses its arguments: globalOptions in packages/cli/src/bin/cli.ts scans the arguments up to a -- for --json and --no-color. So the sink exists before stricli runs, and what stricli refuses (an unknown command, a bad flag) is an NDJSON error too, with stricli's own writes to stderr dropped (stderr: { write: () => true }). Arguments after -- are the command's own.
  • --no-color is global: it is taken out of the arguments (no command declares it), turns chalk off, and sets NO_COLOR=1 in everyapp's environment so cf and the other children it runs print without color too. NO_COLOR in the environment does the same; so does a stdout that isn't a terminal, or TERM=dumb (colorEnabled in packages/cli/src/lib/output/terminal.ts). Messages that a command builds from a table drawn for a person (secret list, email show) go through stripVTControlCharacters first.
  • Child processes never write to stdout. The children everyapp runs itself (pnpm, the manifest's install and build commands, git, the Gateway's build from the checkout) write to a private log file, $TMPDIR/everyapp/<name>-<time>-<pid>.log (mode 0600, directory 0700, name the first two words of the command line), whose first lines are $ <command line> and # in <cwd> (runLogged in packages/cli/src/lib/childLog.ts). When one fails, the run ends with an error whose code is the step's (COMMAND_FAILED by default; the deploy's install step uses INSTALL_FAILED), whose message is <what> failed, then <command> exited with code <n>. Its last lines: and the last 20 non-blank lines of the child's output indented, then Full log: <path>, and whose data is { command, exitCode, tail, log } (plus fix when the step has one). With --verbose the same output also streams to stderr as it comes, each line dim and indented under $ <command line>. Without --verbose, nothing of it reaches stderr either: packages/cli/src/commands/deploy/stdout.test.ts runs a chatty install under deploy --json and asserts stdout is only events and stderr is empty.
  • cf's own output never goes to the log: the runner keeps it in memory, and a CfError carries its message parsed from cf's error box and its last lines (packages/cli/src/lib/cf/errors.ts).
  • stderr under --json: without --verbose, the one line everyapp itself writes there is the launcher's, Rebuilt the everyapp CLI from this checkout (<commit>) (dim), which packages/cli/bin/everyapp.js prints after it rebuilt dist/ because the checkout's CLI, SDK, perimeter or toolbus sources changed since the last build (<commit> is the checkout's short commit, git describe --always --exclude=*). It also prints on the first run after any command that ran pnpm install in the checkout (create, deploy, release), because the root postinstall rebuilt packages/sdk/dist. A rebuild that fails writes the build output and everyapp: couldn't rebuild from this checkout: … to stderr and exits 1 before any event. Every stdout line is JSON.
  • Help and the version are not events. packages/cli/src/bin/cli.ts answers a bare --help, -h (stricli's --help-all and -H are read as --help, and help [<command>] as <command> --help), --version, -v, a bare group name and <group> --help (pnpm everyapp secret) with text on stdout before the sink exists; <command> --help (-h anywhere among a command's arguments) is stricli's help for that command, text on stdout too (stricli's scanner stops routing at the first help flag and prints the help of the command reached).
  • The runtime is checked before any command: under Bun, or a Node older than 22.18.0, the run is one error with code UNSUPPORTED_RUNTIME through the same sink (packages/cli/src/lib/runtime.ts).

Event types

Six type values. Each event has v and ts; the rest is per type.

type Fields Meaning
step step, status, message?, data? A named piece of work started, finished, was skipped or failed.
progress step, message, data? A line while a step runs (a wait, a log line).
warning code, message, step?, data? Something to know; the run goes on and the exit code is unchanged.
action_required action, message, step?, data? Only a person can go on from here (see the actions below).
error code, message, retryable, step?, data? The run failed; always the last event, exit 1.
done message?, data? The run finished; always the last event, exit 0. display is never in --json.

In every message, the first line is the headline and the lines after it are detail (a person sees them dim under it).

step

step is a dotted name (setup.auth, app.build, gateway.deploy, db.query); status is one of start, ok, skip, fail. fail says the step broke, before the run says why; it never sets the exit code by itself (EventSink.exitCode reads only error and action_required). Under --json it comes from create: create.install fail is followed by the error INSTALL_FAILED; create.types, migrations.local and create.git fail are followed by the warnings WORKER_TYPES_FAILED, MIGRATIONS_FAILED and GIT_FAILED and the run goes on to done (a create.git error that isn't GIT_FAILED is thrown instead). The other fail sites are everyapp dev's, which has no --json. A step's facts are in its data, on the ok (or skip) event. The ones an agent reads most:

step status data
cloudflare.login (login) ok / skip { email }; skip adds token (CLOUDFLARE_API_TOKEN is set)
setup.auth (setup) ok { email }
setup.account ok { id, name, email, gateway } (gateway: a live Gateway is there already)
setup.plan ok / skip { plan }, skip adds url
setup.domain ok { gatewayHost, appBaseDomain, zone }
setup.gateway start { change, release, releaseName, from, gatewayUrl, account }; change is install, resume, upgrade, repair or redeploy as GatewayChange names them
setup.gateway ok / skip { versionId, release, releaseName }; skip with upToDate: true when nothing was deployed
setup.owner ok / skip { email, organizationName } (ok: created by this run's link; skip: there already)
app.register (deploy) ok { appId, workerName, hostname, status, existingApp, firstDeploy, defaultAccess, deployerAccess, hasOwner, publicRoutes, scope, organizationId, organizationName, gatewayUrl } (who can open the app: below; publicRoutes: the manifest's public routes as [{ path, methods }])
app.migrations (deploy), gateway.migrations (setup, update) ok { applied, fresh, restoreCommand?, rollbackCommand? } (D1; below); a Postgres app: { dialect: "postgresql", engine, applied? }
app.live (deploy) ok { hostname, url, waited? }
app.live skip { hostname, url, reason }; reason is outside-base, certificate or first-visit
app.bind ok { versionId, newHosts }
app.access (deploy, delete) ok / skip { appId, applicationId, changed, removed }; only when Cloudflare Access fronts the apps (syncAppsAccessAfter in packages/cli/src/lib/access/sync.ts, after app.live on a deploy, before app.delete on a delete): ok when the apps' Access application's destinations changed to match the app's public routes, skip when they matched already; removed: true on a delete
gateway.access (update, setup) ok / skip { changed: [application ids], gateway: { id, outcome } | null, apps: { id, outcome } | null, combined, missing: [hosts], appsWithRoutes, appsWithMcp }; only on a live self-hosted Gateway behind Cloudflare Access (keepAccessAfterGatewayDeploy in packages/cli/src/lib/access/applications.ts, after the Gateway upload or the up-to-date check, under the access lease): ok when an application was updated to what this release lets through (its bypass paths, its sign-in methods, the policy), skip when both matched already; missing names a host whose application is gone (warning ACCESS_APPLICATION_MISSING)
gateway.upgrade start / ok { from, to } / { to }
app.release (release) ok { tarball, shown, appId, version, sha256 }
db.query start / ok { database }
db.restore ok { appId, databaseId, bookmark, migrations, createdAt, createdBy }

app.register on a deploy (packages/cli/src/commands/deploy/deployApp.ts) says who can open the app, the Access fact a person sees at the end of a first deploy (the human sink's reading is in packages/cli/src/lib/output/events.ts): scope: "shared" means every organization's members (no command sets it now; a redeploy keeps the scope the registry has: registerFromManifest in packages/cli/src/commands/appGateway.ts passes none, so register in packages/cli/src/lib/gateway/registry.ts keeps the row's, org for a new app); else defaultAccess: true means everyone in organizationName; else deployerAccess: false means owners and admins only, and true the deployer, owners and admins (changed at <gatewayUrl>/admin/apps). hasOwner: false means the Gateway has no owner yet and its first owner gets the app. firstDeploy is true until the app has been live once; existingApp says the registry had it already.

app.migrations and gateway.migrations ok (migrationEventData in packages/cli/src/lib/migrations/remote.ts): applied lists the migrations applied this run (empty when the database was up to date); fresh: true means the database had none of them before, so there was nothing to restore and no restore point was saved; whenever one was, restoreCommand is <CLI> db restore … as a person runs it and rollbackCommand cf's own command, the same rollbackCommand an error carries (see after a migration, below). A Postgres app (migrations.dialect: "postgresql") gets { dialect: "postgresql", engine: "drizzle" | "prisma", applied? } and never a restore point; applied only with Prisma, which names what it applied.

progress

Emitted while a step runs, with the step's name. A wait for a lease (app.lease, gateway.lease, db.lease; data: { holder }, packages/cli/src/lib/gateway/lease.ts), for a certificate (gateway.tls, packages/cli/src/lib/domains/certWait.ts, two shapes: { host } when one hostname's certificate arrived, message https://<host> has its certificate., which a person's sink doesn't show, and { hosts, waitedMs, waitingFor } while others wait, waitingFor a { "<host>": "tls" | "dns" | "other" } map of why each doesn't answer yet), for an app to go live (app.live, { hostname, waitedMs, answered } every 15 seconds, answered: true once the hostname answers over HTTPS and the wait is for the app itself rather than its certificate, packages/cli/src/commands/deploy/steps/waitForLive.ts), a release download (app.release), the mail routes being applied (app.email), everyapp dev's request and tool-call lines (dev.session, dev.mcp). On Ctrl-C the NDJSON sink emits one progress with step: "interrupt" whose message is built from what is running and registered (stoppingMessage in packages/cli/src/lib/interrupt.ts): stopping cf while cf children run, then each registered cleanup's label, joined and capitalized (Stopping cf and freeing <app> for other runs), or Stopping when nothing is registered; then the process exits (see the exit codes).

warning

code is stable, message is for a person, step names the step it came from when there is one. The sink writes a warning once per run per code and message pair: a check run for the app and then for the Gateway that raises the same words is written once (the emitter keys on `${code}\n${message}`). A warning's data can carry a next step the same way an error does: OWNER_NOT_CLAIMED has rerun: "<CLI> setup", rerunNote: "at a terminal" and needsTerminal: true (needsTerminal means the rerun is for a person at a terminal: hand it over; run with --json where CI is set, it raises the same warning again); ACCESS_NOT_CHECKED and ACCESS_SYNC_FAILED (step app.access, on a deploy or delete, syncAppsAccessAfter in packages/cli/src/lib/access/sync.ts) have appId and rerun: "<CLI> access on", the first with a rerunNote saying when; EMAIL_INBOUND_UNDECLARED (on a deploy) has rerun: "<CLI> email unset inbound"; MIGRATIONS_FAILED (on create) has rerun: "<CLI> db migrate"; WORKERS_PAID_UNVERIFIED has url and plan; GATEWAY_AT_ZONE_APEX has suggestedDomain; APP_NEEDS_SETUP (on a deploy, step app.secrets: <app> won't open until <names> is set, the first unmet setting's why under it) has setup, the AppSetup under done below, and the run's done.data.next carries its configure command. A warning never changes the exit code. CUSTOM_DOMAIN_OWNER_UNCHECKED is hidden from a person without --verbose but is always in the stream.

action_required

action is one of the eight values below. data fields:

Field Meaning
url The page the person opens.
code A one-time code to enter at url (device login).
expiresAt ISO time after which url (or code) stops working.
waiting true: the run waits for the action and goes on by itself once it is taken. Absent: the run stops here (exit 2).
waitingFor What the wait is for, in words (Cloudflare login, the owner account (up to 15 minutes)); a person's spinner says Waiting for <it>.
rerun The command to run once the action is taken: this same command, --json included when this run had it, unless the action says another or needsTerminal is set. Run it as it is.
rerunNote Said after rerun (in a terminal, to finish).
needsTerminal true: only a person at a terminal can do rerun, so it is built without --json. Hand it over as it is; never add --json (rerun_interactive; the warning OWNER_NOT_CLAIMED carries it too).
choices Instead of rerun, when the way on is one of several commands: an array of { rerun, label, … }, one per choice.

A stopping action always has rerun or choices. A waiting action has neither, because the run continues; if the wait runs out, the run ends: claim_owner with a last stopping action_required (exit 2), cloudflare_login with the error CLOUDFLARE_LOGIN_FAILED (exit 1). Action-specific keys (accounts, zone, plan, workerName, …) are listed per action below, and rollbackCommand is added after a migration (see below).

error

code is stable (the codes are on /docs/agent/errors), message is for a person, retryable says whether running the same command again can succeed with nothing changed. data fields:

Field Meaning
fix A remedy in words, not a command (use a name with lowercase letters, digits and dashes).
rerun The command to run next, where one helps (see the rules below).
rerunNote Said after rerun (once it's done, for a stack trace to report).
authorize true: rerun takes something over (a flag like --force-dns, --yes). Show it and ask; never run it on your own.
url A page that helps (ZONE_NOT_FOUND, from gatewayZone in packages/cli/src/commands/setup/deployGateway.ts, carries the Cloudflare page to add a domain).
stack The thrown error's stack, on UNEXPECTED.
rollbackCommand cf's own command that puts a database this run migrated back to its restore point.
didYouMean A close match to what was typed (argument errors).
usage The command's usage line (argument errors).
help The --help command to run (argument errors).

Plus what the error raised with: command, exitCode, tail and log on a failed child; accounts on GATEWAY_NOT_FOUND and install_gateway; holder on LEASE_HELD; zones on ZONE_NOT_FOUND and DOMAIN_REQUIRED (with choices when there are several zones); url and cloudflare on CF_EMAIL_UNVERIFIED; domains and commands on EMAIL_ROUTING_DISABLED; live and candidate on DOWNGRADE_REFUSED.

done

The run's last event on success. data always has durationMs, the milliseconds since the sink was created (the emitter adds it). Two conventions across commands:

  • next: what to run next, an array whose items are a command string or { command, note } (setup, create, deploy, install, release, configure, email show, secret list, mcp add, mcp on, mcp list, mcp show); status adds code and why to each and may give a page to open as url instead of a command ({ code, command?, url?, note, why }, under status --json).
  • facts: what a person sees as labelled lines, an array of { label, value, note? } (setup's Gateway and Admin).

The rest is the command's result (see the commands below).

Two commands carry an app's setup state as setup, an AppSetup (packages/sdk/src/internal/appSettings.ts; in status it is null when the app declares nothing to set, is still pending or its Worker is gone): status as data.apps[].setup, secret list as data.app.setup.

{ ready: boolean,
  settings: Setting[],            // every setting, manifest order
  unmet: Setting[],               // the ones that keep the app closed
  groups: [{ group, met, members: string[] }],
  command: { command } | null }   // "pnpm everyapp configure <app>"; null once ready
Setting = { kind: "provider" | "capability" | "secret" | "ai-routes", id, label,
  holder: "gateway" | "app", names: string[],
  need: { kind: "required" | "optional" | "anyOf", group? },
  set: true | false | null,       // null: the app Worker's names were never recorded; never blocks
  blocking: boolean, why?, hint?, docs?, setup? }
  // hint: what the key is and where it comes from, from the provider's entry (its hint, then each setting's own)
  // setup: the guide, "setup/<name>.md" in the everyapp skill; none for a provider registered on the Gateway

An agent reads unmet, shows each setting's why, hint, docs and setup guide (a provider registered on the Gateway has no guide: pnpm everyapp provider show <id> prints its spec, /docs/agent/providers), and gets the values in without one passing through the chat: the person runs pnpm everyapp configure <app> in their own terminal; or NAME=value lines a command or a file produced are piped into pnpm everyapp configure <app> --json; or the person exported them under the settings' own names before the agent started and the agent runs pnpm everyapp configure <app> --from-env --json (one name: secret set NAME --from-env NAME --json). It never asks for, prints or repeats a value. configure ends with data.app.setup read again; deploy ends with one next entry carrying the same command (note asks for each; <app> opens once they are set) when the app it deployed needs setup, beside its APP_NEEDS_SETUP warning. An MCP app (mcp on), which has nothing to configure, carries pnpm everyapp secret set <KEY> instead.

Exit codes

Code When
0 The run ended with done.
1 An error was emitted, whatever came before it (a waiting action that was taken, steps that succeeded).
2 The run stopped on an action_required without waiting, or ended while still waiting for one. Do the action, then run data.rerun (or one of data.choices).
130 Ctrl-C (SIGINT) while cf children or cleanups were registered (packages/cli/src/lib/interrupt.ts).
143 SIGTERM, the same way.
251 stricli found no command for the arguments (UNKNOWN_COMMAND).
252 stricli refused the arguments (UNKNOWN_FLAG, INVALID_VALUE, MISSING_ARGUMENT, MISSING_FLAG_VALUE, TOO_MANY_ARGUMENTS, REPEATED_FLAG).

The sink computes 0, 1 and 2 (EventSink.exitCode): 1 once an error was emitted; else 2 once the run stopped on an action, or while it waits for one (until done clears it); else 0. runReported in packages/cli/src/lib/output/run.ts sets it with setExitCode, and packages/cli/src/bin/cli.ts re-applies it after stricli's run(), which would otherwise reset process.exitCode. 251 and 252 are stricli's own and stay. stricli's ExitCode values (@stricli/core) are negative, UnknownCommand: -5, InvalidArgument: -4, ContextLoadError: -3, CommandLoadError: -2, InternalError: -1 (and CommandRunError: 1), and reach the shell as 256 plus the value: 251, 252, 253, 254, 255. 254 is COMMAND_FAILED from a command loader that failed (below). 253 (ContextLoadError) is returned only when a context loader stricli calls fails, and packages/cli/src/bin/cli.ts hands run() a context already built (buildContext), so it cannot occur; 255 (InternalError) is in the table and returned nowhere in @stricli/core 1.3.0. Neither has an everyapp event.

Ctrl-C (packages/cli/src/lib/interrupt.ts): the first SIGINT or SIGTERM forwards the signal to every running cf child (its process group), waits up to 3 seconds for them, kills what's left, runs the synchronous cleanups (temp directories, secrets files), then the asynchronous ones for up to 10 seconds (recording an upload, releasing the deploy lease, last registered first), and exits 130 (143 for SIGTERM). A second Ctrl-C exits at once; a SIGTERM after the first signal (a CI runner following its SIGINT up) lets the cleanup go on. With nothing registered, Node's default handling applies. everyapp dev stops its own cf dev through a cleanup and exits 130 too.

Argument errors (exit 251 and 252)

What stricli's parser and router refuse is reported as an error event with retryable: false and a code from ArgumentErrorCode (packages/cli/src/argumentErrors.ts), the exit code staying stricli's.

code Exit When data
UNKNOWN_COMMAND 251 No route for the input; message lists the group's commands. didYouMean (a full command, when a visible name is within two edits), help (<CLI> <group> --help)
UNKNOWN_FLAG 252 --flag or -x the command doesn't declare. didYouMean (--<flag>, within two edits), help
INVALID_VALUE 252 An enum flag given another value; a value its parser refused; a value given to a switch. didYouMean (the enum value), usage (a positional), or fix (use --flag or --no-flag)
MISSING_ARGUMENT 252 A required positional is missing (Missing the SQL to run, Name at least one …). usage
MISSING_FLAG_VALUE 252 --flag needs a value when the flag was typed without one; <command> needs --flag <value> when a required flag wasn't given. usage (required flags shown in it), help
TOO_MANY_ARGUMENTS 252 More positionals than the command takes. usage
REPEATED_FLAG 252 A flag given more than once. help
COMMAND_FAILED 254 / 1 stricli's exit code: 254 when the command's loader failed (exceptionWhileLoadingCommandFunction, CommandLoadError), 1 when its body threw or returned an Error outside runReported (exceptionWhileRunningCommand, commandErrorResult, CommandRunError). help

usage reads <CLI> <command path> [<arg>] [flags] (pnpm everyapp create [<name or path>] [flags]). <CLI> is how this run was started: pnpm everyapp under pnpm, npx everyapp under npm, yarn everyapp under yarn, else everyapp (invocation in packages/cli/src/lib/output/command.ts, from npm_config_user_agent).

Actions a person must take

RequiredAction (the REQUIRED_ACTIONS array in packages/cli/src/lib/output/events.ts) has eight values: cloudflare_login, register_workers_subdomain, claim_owner, upgrade_workers_paid, rerun_interactive, add_domain, choose_account, install_gateway. For each: who raises it, what the person does, what data carries, and whether the run waits.

cloudflare_login

cf isn't logged in to Cloudflare and CLOUDFLARE_API_TOKEN isn't set. Raised by every command that reaches Cloudflare (ensureCfAuth in packages/cli/src/lib/cf/account.ts).

  • Under --json, a command that hands the device login on (login, setup, update, status, deploy, install, delete, secret, email, access, provider, db query, db restore) runs cf auth login --no-browser piped and, as soon as cf prints its link and code, emits action_required with action: "cloudflare_login", step the command's login step (cloudflare.login, setup.auth, gateway.login, app.login, db.login, login), message Log in to Cloudflare: open this link and enter the code, and data: { url, code, expiresAt, waiting: true, waitingFor: "Cloudflare login" }. expiresAt is 5 minutes from then, the time cf gives (its own cap). The run waits: pass url and code to a person and keep reading. Approved in time, the login step ends ok with { email } and the run goes on. Not approved, the run ends with error CLOUDFLARE_LOGIN_FAILED (retryable: true, rerun: "<CLI> login", message Cloudflare login timed out after … with Or set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID.).
  • A path that doesn't hand the login on stops instead: action_required cloudflare_login with no waiting, message Log in to Cloudflare with <CLI> login / Unattended? Set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID instead., rerun this command.
  • CLOUDFLARE_API_TOKEN set but refused is not an action: error CLOUDFLARE_TOKEN_REJECTED with fix.

register_workers_subdomain

cf refused an upload because the Cloudflare account has no workers.dev subdomain yet (cf error kind subdomain_missing, explainDeployFailure in packages/cli/src/lib/cf/deploy.ts). Raised by whatever uploads a Worker: deploy, setup, update, and the Gateway rebind delete runs. Message Choose a workers.dev subdomain for this Cloudflare account / Cloudflare needs one before an account's first Worker. Apps never use it. data: { url, rerun }: url is the dashboard link in cf's message, else https://dash.cloudflare.com/<accountId>/workers/onboarding; rerun this command. Does not wait.

claim_owner

The Gateway has no owner yet, and this run minted the one-time setup link that creates one (ensureOwner in packages/cli/src/commands/setup/runSetup.ts, packages/cli/src/lib/gateway/setup-link.ts). Raised by setup, and by deploy when it installs a Gateway (installGateway includes it).

  • First, action_required with action: "claim_owner", step: "setup.owner", message Open this link to create the owner account / Works once and expires in 24h. Treat it like a password. (or Open this new link … / The link shown before no longer works. … when replaced), and data: { url, expiresAt, replaced, waiting: true, waitingFor: "the owner account (up to 15 minutes)" }. url is <gatewayUrl>/setup#token=est_…: the secret is in the fragment, only its SHA-256 is in the Gateway's D1, and opening it makes that person the Gateway's owner. Hand it to the person; never log it. expiresAt is 24 hours from minting. The run waits: it reads the link's row in D1 every 5 seconds for up to 15 minutes. Claimed, setup.owner ends ok with { email, organizationName } and the run goes on to done.
  • Nobody opened it in 15 minutes: a last action_required claim_owner with message Open the setup link above to create the owner account / Nobody opened it within …. It still works for …; running setup before it's opened replaces it., data: { setupUrl, expiresAt, rerun: "<CLI> setup --json", rerunNote: "to finish" } (the rerun is <CLI> setup, with --json when this run had it); exit 2.
  • The link stopped working (setup ran again elsewhere, or it expired): action_required claim_owner, message Use the newer setup link, or get a new one, rerun: "<CLI> setup --json" (the same rule), rerunNote: "for a new link".
  • The link is shown to a person at the terminal or through --json (maySetupLinkBeShown: relayed), never when CI is set: then the run ends done (exit 0) with data.owner: null and the warning OWNER_NOT_CLAIMED (rerun: "<CLI> setup", rerunNote: "at a terminal", needsTerminal: true): hand rerun to the person; run again with --json where CI is set, it ends the same way.
  • Before the link, a plain request to the Gateway can warn GATEWAY_UNREACHABLE (data: { kind, reason }; the link works as soon as it answers) or ACCESS_IN_FRONT (Cloudflare Access protects it; whoever opens the link signs in to Access first).

upgrade_workers_paid

The account is on Cloudflare's Free plan; EveryApp needs Workers Paid. Three sources, none waits, rerun is always this command:

  • setup on a new install reads the plan before anything is created (checkPlan in packages/cli/src/commands/setup/runSetup.ts): message Subscribe this account to Workers Paid / It's on the Free plan. …, data: { plan, url, rerun } with url https://dash.cloudflare.com/<accountId>/workers/plans. A plan that can't be told is the warning WORKERS_PAID_UNVERIFIED instead; one cf couldn't read is the step setup.plan skip.
  • cf refused an upload for the plan (cf error kind free_plan, packages/cli/src/lib/cf/deploy.ts): data: { workerName, cloudflare, url, rerun }, cloudflare being cf's message.
  • Any other cf call refused for the plan (packages/cli/src/lib/output/report.ts): message Cloudflare refused, for the Free plan: <cf's message> / EveryApp needs Workers Paid ($5 a month, then usage). Subscribe, then run this command again., url https://dash.cloudflare.com/?to=/:account/workers/plans.

rerun_interactive

A Worker's live version wasn't deployed by everyapp (changed in the dashboard, through the API, or by an interrupted deploy), and Cloudflare won't replace it unattended; only a person at a terminal can confirm. Raised by whatever changes a Worker (assertReplaceable and explainDeployFailure in packages/cli/src/lib/cf/deploy.ts): deploy for the app's Worker (packages/cli/src/commands/deploy/deployApp.ts), secret set and secret unset for each Worker they change (packages/cli/src/commands/secret/index.ts), and for the Gateway's Worker setup, update and the rebind a deploy or delete runs (reconcileGateway in packages/cli/src/lib/gateway/reconcile.ts, then applyGateway in packages/cli/src/lib/gateway/apply.ts).

  • Before anything changes: message <Worker> 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., data: { workerName, versionId, rerun, rerunNote: "in a terminal", needsTerminal: true }.
  • At the upload (cf's strict mode aborted, kind strict_conflict): message <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., data: { rerun, rerunNote: "in a terminal, and confirm cf's prompt", needsTerminal: true } (no workerName or versionId).

Does not wait. rerun is this same command with --json left out (thisCommand([], { terminal: true }) in packages/cli/src/lib/output/command.ts: the one rerun built for a terminal), and --yes never skips it (packages/cli/src/commands/flags.ts). data.needsTerminal: true is the field to key on, not the action name: it means only a person at a terminal can do the rerun (needsTerminal in ActionData, packages/cli/src/lib/output/events.ts; the warning OWNER_NOT_CLAIMED carries it the same way). Hand rerun to the person as it is: an agent that adds --json back gets this same action_required again, every time, because only a person at a terminal can answer the confirmation.

add_domain

The Cloudflare account has no active zone a Gateway can live under. Raised by setup (and deploy installing a Gateway) from activeZones in packages/cli/src/commands/setup/runSetup.ts and gatewayZone in packages/cli/src/commands/setup/deployGateway.ts:

  • A zone exists but isn't active: message Finish activating <zone> on Cloudflare / Its nameservers don't point at Cloudflare yet., url is the zone's dashboard page, https://dash.cloudflare.com/<accountId>/<zone> (zoneUrl in packages/cli/src/lib/domains/zones.ts); from gatewayZone (the zone of --domain) data adds { zone, status }.
  • No zone at all: message Add a domain to this Cloudflare account / EveryApp needs one. Buy one in the dashboard (about $10 a year for a .com, ready in minutes), / or point a domain you own at Cloudflare's nameservers., url is the account's domains page, https://dash.cloudflare.com/<accountId>/domains/overview (registerDomainUrl in packages/cli/src/lib/domains/zones.ts).

rerun this command; does not wait.

choose_account

The login sees several Cloudflare accounts and nothing picked one (no --account-id, no CLOUDFLARE_ACCOUNT_ID, no hostname that singles one out). No single rerun fits: the action is thrown with rerun: null, which reportFailure leaves out, so the event has no rerun and data.choices lists one command per choice. Pick one choice (or let the person) and run its rerun as it is: every choices[].rerun is built by thisCommand (packages/cli/src/lib/output/command.ts) like a plain rerun, so it carries --json when this run had it, and --json given twice is the error REPEATED_FLAG. Does not wait.

  • Installing a Gateway (resolveAccount in packages/cli/src/lib/cf/account.ts): message Choose the Cloudflare account to install in / <email> can use <n> accounts. Run one of:; each choice is { accountId, accountName, label, rerun } with rerun this command plus --account-id <id>.
  • Several accounts have a Gateway (discoverGateway in packages/cli/src/lib/gateway/discover.ts): message Choose which Gateway to use / <email> sees a Gateway in <n> Cloudflare accounts. Run one of:; each choice is { accountId, accountName, gatewayUrl, label, rerun }. rerun is this command plus --account-id <id>, or for setup this command with --domain <gatewayHost> in place of its --domain.

install_gateway

No account the login sees has a Gateway, and the command needs one. Two raisers; does not wait.

  • status (existingGateway in packages/cli/src/lib/gateway/connect.ts): message No Gateway in <account>'s Cloudflare account (logged in as <email>) / Gateway in another account? Log in to it: <CLI> login, data: { accounts, rerun: "<CLI> setup", rerunNote: "installs one" }, accounts being the { id, name } of every account asked. That rerun names another command, so add --json to it yourself; it has no --domain, and under --json the setup it names ends with the error DOMAIN_REQUIRED (pickDomain in packages/cli/src/commands/setup/runSetup.ts): message Choose the Gateway's address with --domain / It must be under one of this account's domains: <zones>. A subdomain works best., data.zones (the account's active zones), and a way on shaped as choose_account's. With one zone: rerun is this command with --domain apps.<zone> added (<CLI> setup --domain apps.<zone>), rerunNote: "or another name under <zone>", fix: "add --domain apps.<zone>, or another name under <zone>"; run data.rerun as it is (thisCommand built it, so it carries --json when the run had it). With several: no rerun, data.choices is one { label: "<zone>", rerun: "<CLI> setup --domain apps.<zone>" } per zone, and fix: "add --domain apps.<first zone>, or another name under <zone>, <zone> or <zone>"; pick one choice (or let the person) and run its rerun as it is (built the same way).
  • deploy (offerGateway in packages/cli/src/commands/deploy/steps/offerGateway.ts): at a terminal it offers to install one at apps.<zone>; under --json it stops with message Install a Gateway first: <account> has none yet / Apps deploy behind one. Setup needs a person once, to create the owner account., rerun <CLI> setup --domain <apps.<first active zone>> [--account-id <id>] (--domain <hostname> when no zone could be read), data: { accounts, domain? }. The deploy comes after the setup.

Every other command that needs the Gateway (secret, email, db, access, provider, delete) connects through connectToGateway and requireGateway (packages/cli/src/lib/gateway/connect.ts, packages/cli/src/lib/gateway/discover.ts) and fails with the error GATEWAY_NOT_FOUND instead: no Gateway in any account the login sees gives rerun: "<CLI> setup --domain <hostname>" and rerunNote: "to install one"; none in the account --account-id or CLOUDFLARE_ACCOUNT_ID named, or a login that lists no accounts, gives a fix naming --account-id; data.accounts always.

What to do next: fix, rerun, authorize

The rules are in packages/cli/src/lib/errors.ts. Every problem says what to do next its own way, never "the command that just failed" by default:

  • fix is a remedy that isn't a command. A validation error says what's wrong and how to fix it, with no rerun.
  • rerun is a command to run, rerunNote the words after it. It defaults to this same command only where running it again can help: an action_required (do the action, then run it again), a retryable error, and a takeover (the command with its flag, authorized). An error can name another command (<CLI> login, <CLI> deploy, <CLI> setup).
  • "This same command" is thisCommand: the arguments as given, --json included when this run had it (so an agent runs the rerun as it is), each quoted for a POSIX shell, prefixed by <CLI>. Some reruns drop a flag (thisCommandWithout("--domain")) or add one. A rerun only a person at a terminal can do (thisCommand([], { terminal: true }): every event with needsTerminal: true) leaves --json out.

Takeover flags

An error whose way on is a flag that takes something over (replaces a DNS record, deletes data, skips a confirmation) gets rerun: "<this command> <flag>" and authorize: true. Show it and ask; never retry with the flag on your own. The table (TAKEOVER_FLAGS):

code Flag Raised when
DNS_RECORD_CONFLICT --force-dns A DNS record answers a hostname about to be published (packages/cli/src/lib/domains/preflight.ts); fix says to delete it or use another name.
DNS_UNREADABLE --force-dns The login can't read the zone's DNS records (a token without Zone DNS Read).
CAA_BLOCKS_ISSUANCE --force-dns In the table; nothing raises it in this checkout.
DURABLE_OBJECT_REMOVED --allow-delete-durable-object-data everyapp.config.ts dropped a Durable Object class the last deploy had (packages/cli/src/commands/deploy/deployApp.ts).
WORKFLOW_REMOVED --allow-delete-workflows It dropped a Workflow the last deploy had.
EMAIL_ROUTING_DISABLED --enable-email-routing An inbound address's domain has Email Routing off (packages/cli/src/lib/app/emailRoutes.ts); data: { domains, commands }.
CONFIRMATION_REQUIRED --yes A confirmation nobody could answer (see prompts below).

DOWNGRADE_REFUSED sets its own (downgradeRefused in packages/cli/src/lib/gateway/apply.ts; the Gateway always comes from this checkout, checkoutGateway in packages/cli/src/lib/gateway/checkoutRelease.ts): setup and update (a hidden flag there) take --allow-downgrade; both reach the refusal through installGateway in packages/cli/src/commands/setup/runSetup.ts with allowDowngradeFlag: true, so the error carries rerun this command with --allow-downgrade, authorize: true and fix: "git pull, then run setup again" (that text from update too). The rebind a deploy or delete runs has no flag (refuseOlderCheckout, same file as downgradeRefused): fix: "git pull" with rerun this command and rerunNote: "after that". data: { live, candidate }.

CANCELLED

code: "CANCELLED" is a person declining a prompt, or pressing Ctrl-C at one: nothing went wrong, so it has no fix or rerun, its message says what was left as it was (Cancelled. Nothing changed., Cancelled. Nothing deleted.), and the run still exits 1. Under --json nothing prompts, so it comes only from runs a person attends.

retryable

retryable: true means running rerun again can succeed with nothing changed. rerun defaults to this same command (EveryAppError in packages/cli/src/lib/errors.ts) but is not always it: run data.rerun, never a command you rebuilt. rerunNote says when (once the other deploy finishes, in a few minutes). The errors raised with it:

code rerun Raised when
CF_TIMEOUT this command A cf call passed its time limit.
NETWORK_ERROR this command The network failed (see how a thrown error becomes an event).
CF_EMAIL_UNVERIFIED this command The Cloudflare account's email address isn't verified (Cloudflare API code 10034), so Cloudflare refuses to create anything; data.url is the profile page, https://dash.cloudflare.com/profile, data.cloudflare cf's message (see how a thrown error becomes an event).
LEASE_HELD this command Another run holds the lease after the wait; data.holder names it; rerunNote once the other deploy finishes on a deploy, else once it's done (<CLI> status shows it) (packages/cli/src/lib/gateway/lease.ts).
LEASE_LOST this command This run's hold expired, so it stopped before its next change to avoid overlapping another run.
APP_CHANGING this command The app's registry row kept changing while the deploy recorded its upload; the upload is kept (packages/cli/src/lib/gateway/registry.ts).
APP_NOT_LIVE <CLI> status The hostname answers but the app isn't live after 10 minutes; rerunNote: "then deploy again" (packages/cli/src/commands/deploy/steps/waitForLive.ts).
APP_NOT_FOUND (from the live wait) this command The app was deleted from the Gateway while the run waited for it; rerunNote: "to put it back".
RELEASE_DOWNLOAD_FAILED this command A release URL timed out or couldn't be fetched (packages/cli/src/lib/release/github.ts).
EMAIL_ROUTES_PARTIAL this command The inbound addresses are saved, routing them stopped part-way; rerunNote again, or <CLI> deploy, to finish; data: { saved, cause }.
SECRETS_PARTIALLY_APPLIED <CLI> secret set <the names left> (or unset; with --app <id> when it ran by --app and one of the app's own is left); from configure, <CLI> configure <app> (rerunNote to set what is left; before the first deploy <CLI> configure, in the app's directory, to set what is left) Some Workers took the change before Cloudflare refused one; data: { applied, left } (packages/cli/src/commands/secret/index.ts).
CLOUDFLARE_LOGIN_FAILED <CLI> login The device login timed out or didn't complete.

retryable: false with a rerun means something must happen first (rerunNote, fix, or an action).

After a migration: rollbackCommand

A deploy or Gateway upgrade saves a Time Travel restore point before it migrates a database that has data (packages/cli/src/lib/migrations/remote.ts). When the run fails after that, afterMigration attaches the restore point to the thrown error and reportFailure adds to the last event, error or action_required alike: a line The database stays migrated. To roll it back: <CLI> db restore … at the end of the message, and data.rollbackCommand, cf's own command for a script: CLOUDFLARE_ACCOUNT_ID=<id> npx cf@<pinned version> d1 time-travel restore <databaseId> --bookmark <bookmark> --force (a PowerShell variant on Windows without SHELL; restoreCommand in packages/cli/src/lib/cf/d1.ts). --force is there because cf, where nobody can type, answers its own confirmation "no". MIGRATION_FAILED itself (the migration broke, nothing uploaded) carries rollbackCommand in data the same way, with fix: "correct the migration". A run that succeeds says the same restore point on the migration step's ok event (restoreCommand and rollbackCommand in app.migrations or gateway.migrations, above), so a deploy that went through can be undone from the stream too.

How a thrown error becomes an event

reportFailure in packages/cli/src/lib/output/report.ts turns whatever a command threw into its last event, in this order:

Thrown Event
ActionRequiredError action_required with its action, data, and url, rerun, rerunNote when set (rerun: null is omitted: choices carries the commands).
CfError of kind free_plan action_required upgrade_workers_paid (see above).
EveryAppError error with its code, retryable, data, and fix, rerun, rerunNote, authorize: true when set.
CfError of kind email_unverified (Cloudflare API code 10034, packages/cli/src/lib/cf/errors.ts) CF_EMAIL_UNVERIFIED: message Verify your Cloudflare account's email address first / Cloudflare refuses to create anything until the address on the account is verified. Open the message Cloudflare sent you, then run this command again., retryable: true, rerun this command, data: { url: "https://dash.cloudflare.com/profile", cloudflare }, cloudflare being cf's message.
CfError (any other kind) error with code CF_<KIND> in upper case: CF_AUTH, CF_NOT_FOUND, CF_ALREADY_EXISTS, CF_ABORTED, CF_STRICT_CONFLICT, CF_SUBDOMAIN_MISSING, CF_R2_NOT_ENABLED, CF_ACCESS_NOT_ENABLED, CF_D1_ID_REJECTED, CF_UNKNOWN; retryable: false; cf's message.
CfTimeoutError (a cf call past its time limit) CF_TIMEOUT, retryable: true, rerun this command.
CfOutputError (cf succeeded but printed something other than the expected JSON) CF_OUTPUT, retryable: false.
ManifestError (from @every-app/perimeter/manifest) INVALID_MANIFEST, retryable: false.
An error (or its cause) with a Node network code: ECONNREFUSED, ECONNRESET, ENOTFOUND, EAI_AGAIN, ETIMEDOUT, UND_ERR_CONNECT_TIMEOUT, UND_ERR_SOCKET; or a TypeError fetch failed NETWORK_ERROR, message The network failed (<code>) then the error's message (the TypeError: The network failed / fetch failed, no code), retryable: true, rerun this command.
Any other error with an upper-case code (ENOENT, EACCES) SYSTEM_ERROR, retryable: false.
Anything else UNEXPECTED: message everyapp hit an unexpected error / <name>: <message> / This is a bug in everyapp, not something you did.; data.stack; and, since the NDJSON sink is never verbose, rerun this command with --verbose added (if not there) and rerunNote: "for a stack trace to report". Under --json the stack is in data already; the rerun is for a person's screen.

step is set on the event when the caller named the step that failed. CF_ABORTED is cf answering its own confirmation "no" where no one could type (it prints Aborted. and exits 0, which everyapp treats as a failure).

Prompts under --json

Every question everyapp asks goes through packages/cli/src/lib/output/prompt.ts, and canPromptInTerminal(json) is the gate: stdin and stderr must be terminals, --json must be off, and CI must be unset. Under --json nothing prompts; each question becomes an event:

  • A yes/no confirmation (confirmUnlessYes): --yes skips it; without it the run ends with error CONFIRMATION_REQUIRED, its message the unattended text (Deleting an app needs confirmation at a terminal, or --yes; This SQL can change <app>'s production database / A person at a terminal confirms it first, or --yes does.; Removing secrets needs confirmation, and there's no terminal to ask in; Turning Access off needs confirmation at a terminal, or --yes; Restoring <db> needs a person at a terminal to confirm, or --yes / It undoes …; anything written to it since is lost too.), rerun this command with --yes, authorize: true, and data when the command has it (db restore: { appId, databaseId, bookmark, migrations, createdAt, createdBy }). Where: delete (with --purge, a person at a terminal types the app id instead; unattended it is the same --yes), db query writing to production (anything that isn't SELECT, EXPLAIN, VALUES, a read-only WITH or a PRAGMA that sets nothing, readsOnly in packages/cli/src/commands/db/queryOutput.ts), db restore, secret unset, access off (turnAccessOff in packages/cli/src/commands/access/index.ts; nothing to confirm when Access isn't in front: done with { removed: false }).
  • teardown's confirmation is the account id, never --yes: typed at a terminal, or --confirm <id> (the same id as --account-id). Under --json without it: error TEARDOWN_CONFIRMATION_REQUIRED (Tearing down <id> needs its id typed at a terminal, or --confirm <id>), rerun this command with --confirm <id> added, authorize: true, data: { accountId, plan }. Only for a dedicated test account.
  • A choice (which account, which Gateway, which zone): choose_account with choices, or for setup without --domain the error DOMAIN_REQUIRED (data.zones, and rerun or choices as under install_gateway above).
  • A secret's value (secret set): never from the command line; --from-env <VAR> or stdin when something is piped in (packages/cli/src/lib/secretValue.ts). With neither, error SECRET_VALUE_REQUIRED (Can't ask for <NAME>'s value without a terminal / Or pipe it in: echo "$<NAME>" | <CLI> secret set <NAME>), rerun: "<CLI> secret set <NAME> --from-env <NAME>". --from-env and stdin hold one value: several names are SECRET_SEVERAL_NAMES.
  • An app's settings (configure <app>): NAME=value lines on stdin (one per line; blank lines and # lines skipped, an export prefix allowed, one pair of quotes around a value dropped, the value otherwise as it is; a later line for a name wins) or --from-env (each unset setting from the environment variable of its own name; a set name is never replaced, and stdin isn't read), never both; a value after the verb is VALUE_ON_COMMAND_LINE (a second argument or --from-env=<value> is refused by the argument parser first, TOO_MANY_ARGUMENTS or INVALID_VALUE, the value never shown), a line that isn't NAME=value PIPED_LINE_INVALID (never echoed), a piped name the manifest doesn't declare SECRET_NOT_DECLARED (echoed only when it looks like a setting name), a blank piped value SECRET_EMPTY, each before anything is written. With nothing read and a required setting unset, error SECRET_VALUE_REQUIRED (Can't ask for <app>'s settings without a terminal / <app> needs <names>. Run this at a terminal, pipe NAME=value lines in (one per line), or export them under those names and add --from-env (which also takes the variable of any unset optional setting).), data.names the names still missing, rerun: "<CLI> configure <app> --from-env" (rerunNote with the variables exported) once deployed; before the first deploy, in the app's directory, rerun: "<CLI> configure --from-env" (in the app's directory, with the variables exported).
  • Turning Email Routing on for a domain: EMAIL_ROUTING_DISABLED with --enable-email-routing (a takeover).
  • Replacing a Worker changed outside everyapp: rerun_interactive; --yes doesn't cover it.
  • cf's own questions: cf runs piped and can't ask; its strict-mode abort is rerun_interactive, and anything else it aborts is CF_ABORTED.

Commands whose result is the done event

For these the done event is the answer; a person sees a table (display), a script reads data.

db query --json

The flag is resultJsonFlag (packages/cli/src/commands/flags.ts, brief Print the result as an NDJSON event), which db query declares with its own brief Print one NDJSON done event, its rows in data.results, instead of a table (packages/cli/src/commands/db/command.ts). data:

{ "database": "production" | "local", "results": [[{ "<column>": <value> }, …], …], "meta": [{ "duration": <ms>, "changes": <n> }, …], "durationMs": <ms> }

results has one array per statement (the SQL is split on ;), each an array of row objects keyed by column; meta has one { duration, changes } per statement, D1's own numbers. A write with no rows back shows as changes > 0. Before it, step db.query start and ok with { database }. A statement D1 refuses is error QUERY_FAILED with the SQLite code in the message. Writes to production need --yes under --json (CONFIRMATION_REQUIRED otherwise).

status --json

data is the whole report (reportStatus in packages/cli/src/commands/status/index.ts): deployed, installed, gatewayUrl, accountId, mode (the install row's deploymentMode: self_hosted, hosted, or null when no upload recorded one, packages/cli/src/lib/gateway/install.ts), release: { version, sha256 }, versionId (the Worker serves), recordedVersionId (the install row names), owner: { email, organizationName } | null, claimed, apps (each { id, hostname, workerName, status, bound, deployedBy, email: { senders, inbound, routed, declaresInbound }, publicRoutes, mcp, setup }; setup is the AppSetup above, null when the app declares nothing to set, is still pending or its Worker is gone), desiredApps, liveApps, appBaseDomain, domains, liveDomains, inSync, upToDate, checkout: { version, treeSha256 } | null, tls, certificates: [{ host, ready }] | null, access: { on, teamDomain, drift, driftUnread } | null (whether Cloudflare Access fronts the Gateway, read from the Gateway Worker's settings and the D1 record by accessStateOf in packages/cli/src/lib/access/settings.ts, and with on, whether it is in step with this release: drift: { paths: [hosts], signIn: [hosts], missing: [hosts], settings: boolean } from Zero Trust, the live applications against what this release wants (accessDrift in packages/cli/src/lib/access/plan.ts; empty lists and settings: false mean in step, otherwise the problem ACCESS_DRIFT names pnpm everyapp access on), or drift: null with driftUnread: { permission, error } when Zero Trust couldn't be read (permission: true for a login without the Access permissions, said on the row and never a problem; otherwise the problem ACCESS_DRIFT_UNKNOWN); off, both are null; the whole is null for a hosted Gateway, before the first upload, or when cf couldn't read the settings), leases, fixes and next (below). message is one line: Gateway <url>: <n> apps live, nothing to fix or … <n> problems to fix, counting error and warning problems only. A Gateway database with no install row gives { deployed: true, installed: false, accountId, fixes: [<GATEWAY_NOT_INSTALLED>] }.

Each apps[] entry carries publicRoutes: [{ path, methods }], the routes its stored manifest opens to the internet (storedPublicRoutes in packages/cli/src/lib/app/publicRoutes.ts: methods upper-cased, ["GET"] for a route that names none, as the perimeter matches them; an empty array when the manifest has no public), and mcp: { by, why } | null, its stored manifest's mcp (storedMcp; the app's host answers /mcp, by the Gateway's ToolBus or the app's own server); a person sees both as the APP table's PUBLIC column (GET /t/*, POST /t/e, GET /health +2 more · MCP /mcp (gateway)).

fixes is what is off and what to do, one { code, severity, message, run, note?, fix? } per problem (fixData): severity is error, warning, info or settling; message is the headline and its detail lines joined with newlines; run the command that fixes it (null when none) and note the words after it (updates it now, asks for the hostname again, redeploys <version>, asking first); fix a remedy in words when there is one. Every problem is listed, the ones that settle by themselves included, info ones last. The codes (packages/cli/src/commands/status/problems.ts):

code Severity Means run
CHECKOUT_BEHIND warning This checkout's Gateway is older than the live one; deploys from here are refused. git pull
GATEWAY_OUTDATED info This checkout's Gateway is newer than the live release (Your every-app folder has a newer Gateway than the one online); the next new app's deploy updates it too. <CLI> update (note: "updates it now")
NO_OWNER warning Nobody can sign in yet. <CLI> setup
GATEWAY_NOT_INSTALLED warning The database exists, the install row doesn't. <CLI> setup
GATEWAY_NOT_UPLOADED warning The install stopped before its first upload. <CLI> setup
GATEWAY_CHANGED warning The Gateway Worker serves a version setup didn't upload. <CLI> setup
BINDINGS_DIFFER warning The live Gateway doesn't bind or publish what its registry lists. <CLI> setup
APP_WORKER_MISSING error An app's Worker no longer exists. <CLI> deploy from <app>'s directory, or <CLI> delete <app>
APP_NOT_BOUND error The Gateway can't bind the app (its Worker's name is wrong). <CLI> delete <app>
APP_NOT_UPLOADED warning Registered, no deploy finished. <CLI> deploy from the app's directory, or <CLI> delete <app>
APP_BINDING settling Goes live on its first visit. none
APP_NEEDS_SETUP warning A required setting of the app is unset (<app> needs setup; the detail names each unmet setting and its guide). <CLI> configure <app> (setup.command, no note: it asks which member of an any-of group)
SETTINGS_UNKNOWN warning cf couldn't list a Worker's secrets (Couldn't list the Workers' secrets); the settings it holds read set: null in apps[].setup.settings[], never blocking. none
EMAIL_ROUTES_UNKNOWN warning Cloudflare didn't list the Email Routing rules. none
EMAIL_INBOUND_UNDECLARED warning Inbound addresses stored, but the deployed config doesn't turn inbound mail on. none; fix names the config change or <CLI> email unset inbound
EMAIL_ROUTE_PENDING warning An inbound address isn't routed yet. <CLI> deploy from <app>'s directory
EMAIL_ROUTE_MISSING warning A routed address gets no mail. <CLI> deploy from <app>'s directory
CERTIFICATES_UNKNOWN warning Cloudflare didn't list the zone's certificates. none
CERT_PENDING settling A hostname's certificate hasn't come yet. none
ACCESS_UNKNOWN warning Cloudflare didn't say whether Access is on (the Gateway Worker's settings couldn't be read). none
ACCESS_DRIFT warning Access is on, and Zero Trust has other paths let through, other sign-in methods, a missing application, or settings naming other applications than this release wants (access.drift). <CLI> access on (note: brings Access in step with this Gateway)
ACCESS_DRIFT_UNKNOWN warning Access is on, and Zero Trust refused the read that says whether it is in step (access.driftUnread.error). none
DEPLOY_RUNNING settling A deploy holds a lease right now. none

next is what isn't done yet, at most four { code, command?, url?, note, why } (nextStepsFor in packages/cli/src/commands/status/next.ts; the rules are docs/design/2026-10-proactive-next-steps.md): command is the <CLI> … to run or url the page to open (one of the two), note what it does, why the state behind it (an app id, a setting name, a count). A step is a function of state: it goes when the state changes, nothing is stored to dismiss it, and a step never repeats a fix (a key APP_NEEDS_SETUP names is not suggested again, and a step whose command a fix's run already gives is dropped). A person sees them as a Next: block after the fixes, each as its command or URL and its note. The codes, in the order they come:

code When command or url
AI_KEY None of OPENAI_API_KEY, OPENROUTER_API_KEY, WORKERS_AI_API_TOKEN is set on the Gateway while an app lists an AI provider it runs without (optional); the names were read <CLI> secret set <the keys the apps name> (note AI for <apps>)
PROVIDER_OFFER An MCP app's key is set (DATAFORSEO_API_KEY) while the app is off, on an uploaded Gateway, and no deployed app lists its capability (OpenSEO lists dataforseo: the key is that app's) <CLI> mcp on <id> (note says each call spends the provider's credit)
PROVIDER_KEY An MCP app is on and its key isn't set <CLI> secret set <key> (an MCP app has no Worker, so configure <id> refuses it; this is its setup.command)
ACCESS_OFF Self-hosted, uploaded, the owner has claimed, access.on is false (before the claim access on refuses, ACCESS_NOBODY_ALLOWED, and NO_OWNER says setup) <CLI> access on
INVITE The owner is the only member of the install organization, no invitation is pending, and an app is up https://<gateway>/admin/users
CONNECT_AI No AI connection and no live personal token in the install organization, while an app serves tools https://everyapp.com/docs/connect-ai, the connector steps (note names https://<gateway>/mcp, which answers a GET with 405: it is for the client, not a browser)
CREATE_APP Uploaded, no apps <CLI> create my-app
EMAIL_UNSET EMAIL_FROM isn't set (the name may be inside it, so EMAIL_FROM_NAME alone decides nothing) while there is more than one member or an app lists email <CLI> secret set EMAIL_FROM EMAIL_FROM_NAME

The reads behind them, all under the operator's credentials and never over HTTPS: the Gateway Worker's secret names (names only, listed once for Access, each app's settings and these), the MCP app rows of the registry (tier provider, from the same listApps the report makes), and one cf d1 query batch of five counts in the install organization (readOrganizationCounts in packages/cli/src/lib/gateway/counts.ts: members, invitations pending and not expired, mcp_connections not revoked, user_access_tokens not revoked and not expired, app_tool_catalogs serving tools). A read that fails skips the rules that need it (an older Gateway without one of the tables, cf down) and raises nothing. Before the owner claims (owner: null) no counts are read, so no INVITE or CONNECT_AI, and no ACCESS_OFF; a hosted Gateway never gets ACCESS_OFF; nothing is suggested before the first upload except what fixes says. The commands are Gateway-wide (secret set NAME, access on, mcp on): the one command that opens an app, <CLI> configure <app>, is the APP_NEEDS_SETUP fix's, never repeated here.

secret list --json

listSecrets in packages/cli/src/commands/secret/index.ts. Names only, never values. With no app named, outside an app directory: { gateway: { workerName: "every-app-gateway", names, settable, mcpCredentials }, apps: [{ id, setup }] } (names set on the Gateway Worker, settable what can be set there, mcpCredentials each remote MCP app credential name as { name, apps, set }, setup each app's AppSetup; an MCP app is in apps only while it needs setup). For one app (<app> from anywhere, or the directory's): { app: { id, deployed, workerName, declared, workers: [{ workerName, names, missing }], secretNames, settings, setup }, gateway: { workerName, names, needed: [{ name, required, set }], missing, settable }, next? }; workers is the app's Worker then each helper, missing the manifest's secrets nobody set, needed the Gateway settings the app reads (set says the Gateway Worker has it), settings every setting with its state, setup the AppSetup above. next mixes { command, note } objects and command strings: once deployed, setup.command.command (<CLI> configure <app>) while something is unmet; before the first deploy, { command: "<CLI> configure", note: "in the app's directory" } while something is unmet, then <CLI> deploy; absent when nothing is missing.

email show --json

showEmail in packages/cli/src/commands/email/index.ts. { app: { id, workerName, status, deployed }, declares: { sends, receives }, senders, inbound, routed, next? }: senders and inbound are the installation's lists on the Gateway, routed the inbound addresses with an Email Routing rule, declares what everyapp.config.ts says the app does. next is an array of { command, note }: first the list's own fix when there is one ({ command: "<CLI> email unset inbound", note: "clear the stored address" } when addresses are stored but the config no longer declares inbound mail, else { command: "<CLI> deploy", note: "route the list as shown" } when an inbound address isn't routed yet or a removed one still is), then { command: "<CLI> email set --sender '*@<domain>'", note: "send as your own domain" } when senders can be set (the config declares sending and the Gateway isn't hosted) and none is stored, then { command: "<CLI> email set --inbound <address>", note: "receive mail" } when the config declares inbound mail and none is stored; absent when empty. With nothing stored at all, next is those email set entries alone. Not deployed: { app: { id, deployed: false }, declares, next: ["<CLI> deploy"] }.

Other done data

Command data
deploy { url, workerName, versionId, live, organization, publicRoutes, mcp, next? } (liveDone in packages/cli/src/commands/appGateway.ts); mcp is the manifest's mcp as { by, why } (the app's host answers /mcp; a person sees it as the MCP fact, Serves /mcp on its own host (<by>): <why>), null when the manifest sets none; publicRoutes is [{ path, methods }] from the manifest's public (publicRoutesOf in packages/cli/src/lib/app/publicRoutes.ts: in manifest order, methods upper-cased, ["GET"] when a route names none, [] when there is no public), which a person sees as the Public fact after Access (Open to the internet without sign-in: GET /hooks/*, POST /collect, the first six routes, then … and N more (everyapp.config.ts public)); next is { command, note } entries (packages/cli/src/commands/deploy/deployApp.ts): { command: "<CLI> email set --inbound <address>", note: "routes mail to <app>" } when the config declares inbound mail and no address is stored, and { command: "<CLI> email set --sender '*@<domain>'", note: "lets <app> send as your domain" } when the config declares sending, the Gateway isn't hosted (app senders can be set) and none is stored; both can be present. When the app needs setup, { command: "<CLI> secret set <names> [--app <id>]", note: "<app> opens once these are set" } comes too, beside the APP_NEEDS_SETUP warning.
release { tarball, shown, version, sha256, next }, next <CLI> install <shown>.
setup, update { gatewayUrl, adminUrl, organizationName, owner, release, mode, gateway, accessOn, facts?, next? }; mode is self_hosted or hosted; gateway is installed, upgraded, repaired or up-to-date; accessOn is true when interactive setup turned Cloudflare Access on (its done is the step setup.access; the facts then carry Access on and next has no access on), false when declined or failed (ACCESS_SETUP_SKIPPED), null when not asked (--yes, --json, update, hosted); next on a newly installed Gateway or a newly created owner is nextAfterInstall in packages/cli/src/commands/setup/index.ts, each entry { command, note }: self-hosted [<CLI> create my-app, <CLI> access on, <CLI> secret set OPENAI_API_KEY, <CLI> secret set EMAIL_FROM EMAIL_FROM_NAME] (notes start your first app, put Cloudflare Access in front of your Gateway (recommended), AI for apps (or OPENROUTER_API_KEY, WORKERS_AI_API_TOKEN), email invites and password resets; needs Cloudflare Email Sending); hosted [<CLI> create my-app, <CLI> secret set OPENAI_API_KEY, <CLI> secret set EMAIL_FROM] (no Access line: access on refuses a hosted Gateway); on an up-to-date one [{ command: "<CLI> status", note: "see apps, domains and certificates" }]; none after a repair, or when nobody could claim the owner.
create { appId, dir, next }; dir is the new app's absolute path; next is [{ command: "cd <path>" }, { command: "pnpm dev", note: "run it locally" }, { command: "pnpm run deploy", note: "put it live" }], <path> being what to type from where create ran (../my-app for pnpm everyapp create ../my-app; the whole path when it was given absolute or from ~).
delete { appId, workerName, alreadyDeleted, purged, removed, kept }.
db restore { appId, databaseId, bookmark, migrations, createdAt, createdBy }.
email set, email unset { app: { id, workerName, status }, senders: { value, changed }, inbound: { value, changed, routed, removed }, next? }.
secret set, secret unset { gateway?: { names, versions, pinned? }, app?: { id, workerName, names, versions } }, versions the new Worker version per Worker; pinned each remote MCP credential set, { name, origin }, with the one origin the Gateway sends it to; unset of names nobody holds is { removed: false }.
configure { app: { id, deployed, setup }, set: string[], changed?: { gateway?: { names, versions }, app?: { id, workerName, names, versions } }, next? }: setup the AppSetup above, read again after the change; set the names set, in manifest order; changed as secret set's data, absent when nothing was set; next is [{ command: "<CLI> configure <app>", note: "at a terminal, with NAME=value lines on stdin, or with --from-env" }] when the app still needs setup (message Set A. <app> still needs X, the names still missing, beside the APP_NEEDS_SETUP warning; before the first deploy { command: "<CLI> configure", note: "in the app's directory: at a terminal, with NAME=value lines on stdin, or with --from-env" }), or before the first deploy [{ command: "<CLI> deploy", note: "then <CLI> configure <app> for X and Y" }] (message <app>'s Gateway settings are set; its own X and Y come after its first deploy; note in the app's directory when it has no secrets of its own); absent when the app is ready (Set A, B and C. <app> is ready, or <app> is ready).
access on { team: { domain, created, sessionSet, globalSession }, signInMethods: [{ id, type, name, allowed, restrictToAccountMembers? }], identityProviders: { oneTimePinAdded, cloudflareAdded }, policy, applications: { gateway, apps, combined }, settings: { names, outcome, reason }, signIn: { url, pending, first }, next } (turnAccessOn in packages/cli/src/commands/access/index.ts); each application carries allowedIdps and signInAdded; settings.outcome is set or kept, signIn.first pin, idp or both, next the secret set commands that follow.
access off { removed: true, settings, applications: { deleted, trimmed, kept } }; { removed: false } when Access wasn't in front.
access show { on, hosted, team, policy, signIn: { methods, cloudflare, pin, openToAnyCloudflareAccount } | null, applications: { gateway, apps, combined }, desired, drift: { paths, signIn, missing, settings }, settings: { names, current }, linkPending, fixes? } (each application with allowedIdps and signInInSync); nothing changes.
install The same as deploy (deployDone): the installed release is deployed.
mcp add { id, name, url, auth: { kind, secret?, header? }, risk, on: true, was, organization: { id, name }, probe, key, mcp: { url, toolPrefix }, granted, next }; was is new or revived; probe what the one look at the URL found (outcome reachable, needs-credential, redirected or unreachable); key null for a server that takes no credential, else { name, set, bound, boundOrigin } (/docs/agent/mcp).
mcp on { id, name, kind, on: true, was, organization: { id, name }, key, mcp: { url, toolPrefix }, granted, next? }; kind is builtin or remote; was is new, revived (a removed row brought back), off (served again, access kept) or existing; next the secret set for the key or credential when it isn't set.
mcp off { id, name, kind, on: false, wasOn, key }: the row is disabled, its access, tokens and snapshot kept.
mcp remove { id, name, kind, removed, key }: the tombstone (mcp off until 2026-10-25).
mcp list { apps: [{ id, name, kind, status, on, url?, auth?, supported?, tools, key, lastListedAt?, lastError? }], mcp, next? } (providers until 2026-10-25); status is on, off, needs setup or not added; mcp the Gateway's /mcp URL; next lists <CLI> mcp on <id> and <CLI> secret set <key> for what is not added or unset.
mcp show { id, name, kind, status, on, url?, auth?, supported?, risk?, rev?, overrides?, addedBy, addedAt, key, snapshot, tools, mcp, next? }; each of tools is { name, serverName, title, risk, source, on, problem }.
mcp refresh { id, name, kind, refreshed, dropped }.
mcp tool { id, tool: { serverName, name, inListing }, before, override, rev }.
teardown { accountId, dryRun, removed, kept, plan }; with --dry-run nothing is removed and plan says what would be.
provider … add (--oauth included), list, show, presets, connections and disconnect: on the providers page, with what each command does.
login, db migrate Only durationMs; the facts are on their steps.

The loop an agent runs

Read stdout line by line and act on type:

  1. step and progress: keep reading; keep a step's data when it carries a fact you need (app.live's url, setup.owner's email).
  2. warning: keep reading. Show it to the person after the run, with its rerun or url if it has one; a rerun with needsTerminal: true is theirs to run, not yours.
  3. action_required with data.waiting: true: hand url (and code) to the person at once and keep reading; the run goes on by itself when the action is taken, and ends done or error like any run; a wait that runs out ends claim_owner with a last stopping action_required (exit 2) and cloudflare_login with the error CLOUDFLARE_LOGIN_FAILED (exit 1).
  4. action_required without waiting: the run is over, exit 2. Show message, url and code; once the person has done it, run data.rerun as it is (it carries --json when this run had it), or let them pick from data.choices and run that rerun as it is too (it is built the same way); a rerun with data.needsTerminal: true (rerun_interactive) goes to the person as it is.
  5. error: the run is over, exit 1. Show message and data.fix. If data.authorize is true, show data.rerun and ask before running it. Otherwise, if data.retryable is true, wait for what rerunNote says and run data.rerun; if rerun names another command (<CLI> login, <CLI> setup), run or show that, with --json added; if there is a url, open it; if there is rollbackCommand, show it beside message's restore line. UNEXPECTED is a bug: report data.stack.
  6. done: exit 0. Read data (and data.next for what to do after).
run: pnpm everyapp <command> … --json
for each line on stdout:
  event = parse(line)
  switch event.type:
    "step", "progress": continue
    "warning":          remember(event) ; continue
    "action_required":
      if event.data.waiting: tell person(event.data.url, event.data.code) ; continue
      else: stop = event                       # exit code will be 2
    "error":            failure = event        # exit code will be 1
    "done":             result = event.data    # exit code will be 0
wait for exit code
if exit == 2: show stop.message, stop.data.url, stop.data.code
              if stop.data.needsTerminal: hand stop.data.rerun to the person as it is   # rerun_interactive
              else, after the person is done: run stop.data.rerun as it is   (or one of stop.data.choices[].rerun, as it is)
if exit == 1: show failure.message, failure.data.fix
              if failure.data.authorize: ask, then run failure.data.rerun as it is
              else if failure.data.rerun: run it (after what rerunNote says, if retryable)
if exit == 251 or 252: show failure.message, failure.data.didYouMean / usage / help
if exit == 130: the run was interrupted; nothing to parse after the "interrupt" progress line

Two cautions. A rerun that is this same command, and every choices[].rerun, carries --json when this run had it (thisCommand builds them all), so run it as it is: --json twice is the error REPEATED_FLAG. Only a rerun that names another command (<CLI> login, <CLI> setup, <CLI> setup --domain <hostname>, <CLI> deploy) is written without it; add --json to those. A rerun whose event carries needsTerminal: true (rerun_interactive, the warning OWNER_NOT_CLAIMED) is built without it: it is for a person at a terminal, and with --json it ends the same way again. And a claim_owner or cloudflare_login url is a credential for whoever opens it: pass it to the person, don't write it anywhere that persists.

everyapp dev has no --json

everyapp dev is the exception: a long-running, human-facing command. Its flags are --port, --host, --mode, --gateway, --base-host (declared baseHost; --help prints the kebab form and the parser takes both, stricli's caseStyle in packages/cli/src/app.ts) and --skip-migrations, with no --json (packages/cli/src/commands/dev/command.ts), and it always writes for a person (eventSink(false) in packages/cli/src/commands/dev/index.ts): the ready line, how to reach the app, then its request and tool-call lines (dev.session, dev.mcp) as they come. Ctrl-C stops the app's dev server and exits 130. Drive it from an agent as a background process and read its text; use /docs/agent/dev for what it prints.

The event log: EVERYAPP_EVENT_LOG

A dormant seam for test harnesses and bug reports, never for an agent's own use. With EVERYAPP_EVENT_LOG=<path> in the environment, every everyapp process appends to that file, one JSON object per line, whether or not it was run with --json: { "type": "invocation", "pid", "ts", "argv", "cwd", "json", "isatty": { "stdin", "stdout", "stderr" }, "ci" } once at start, then each event as the run's sink emitted it (the same object its --json line carries, plus pid; a warning once, done with durationMs and no display), then { "type": "exit", "pid", "ts", "code", "ms" }. What a command never emits is not there either: setup mints its owner link only when maySetupLinkBeShown allows (packages/cli/src/lib/gateway/setup-link.ts), so the link is absent from the log exactly when a person would not have seen it. Each line is one fs.appendFileSync (O_APPEND), so the processes that share a file (pnpm run deploy running everyapp, npx everyapp) interleave whole lines, told apart by pid. The file is created readable by the user only (mode 600), because the owner link in it is a password. A path that can't be written is said once on stderr and the run goes on (packages/cli/src/lib/output/eventLog.ts). Unset, nothing is written. It is how the onboarding loop (docs/design/2026-10-onboarding-loop.md) times a harness's everyapp runs and claims the owner link from outside the harness, and what to ask for in a bug report: EVERYAPP_EVENT_LOG=/tmp/everyapp-events.ndjson pnpm everyapp setup …, then attach the file after reading it for anything secret (an owner link, est_…, is a password).

Sources

  • packages/cli/src/lib/output/events.ts
  • packages/cli/src/lib/output/eventLog.ts
  • packages/cli/src/lib/output/report.ts
  • packages/cli/src/lib/output/prompt.ts
  • packages/cli/src/lib/output/run.ts
  • packages/cli/src/lib/output/command.ts
  • packages/cli/src/lib/output/terminal.ts
  • packages/cli/src/lib/output/text.ts
  • packages/cli/src/lib/errors.ts
  • packages/cli/src/lib/exit-code.ts
  • packages/cli/src/lib/interrupt.ts
  • packages/cli/src/lib/childLog.ts
  • packages/cli/src/lib/runtime.ts
  • packages/cli/src/lib/secretValue.ts
  • packages/cli/src/lib/cf/account.ts
  • packages/cli/src/lib/cf/deploy.ts
  • packages/cli/src/lib/cf/errors.ts
  • packages/cli/src/lib/cf/d1.ts
  • packages/cli/src/lib/cf/schemas.ts
  • packages/cli/src/lib/migrations/remote.ts
  • packages/cli/src/lib/gateway/setup-link.ts
  • packages/cli/src/lib/gateway/connect.ts
  • packages/cli/src/lib/gateway/discover.ts
  • packages/cli/src/lib/gateway/apply.ts
  • packages/cli/src/lib/gateway/change.ts
  • packages/cli/src/lib/gateway/lease.ts
  • packages/cli/src/lib/gateway/registry.ts
  • packages/cli/src/lib/gateway/install.ts
  • packages/cli/src/lib/gateway/reconcile.ts
  • packages/cli/src/lib/access/sync.ts
  • packages/cli/src/lib/access/settings.ts
  • packages/cli/src/lib/release/github.ts
  • packages/cli/src/lib/domains/zones.ts
  • packages/cli/src/lib/domains/certWait.ts
  • packages/cli/src/lib/domains/https.ts
  • packages/cli/src/lib/domains/preflight.ts
  • packages/cli/src/lib/app/emailRoutes.ts
  • packages/cli/src/argumentErrors.ts
  • packages/cli/src/app.ts
  • packages/cli/src/bin/cli.ts
  • packages/cli/src/commands/flags.ts
  • packages/cli/src/commands/appGateway.ts
  • packages/cli/src/commands/setup/runSetup.ts
  • packages/cli/src/commands/setup/deployGateway.ts
  • packages/cli/src/commands/setup/index.ts
  • packages/cli/src/commands/deploy/index.ts
  • packages/cli/src/commands/deploy/command.ts
  • packages/cli/src/commands/deploy/deployApp.ts
  • packages/cli/src/commands/release/bundleApp.ts
  • packages/cli/src/commands/deploy/stdout.test.ts
  • packages/cli/src/commands/deploy/steps/offerGateway.ts
  • packages/cli/src/commands/deploy/steps/waitForLive.ts
  • packages/cli/src/commands/create/index.ts
  • packages/cli/src/commands/delete/index.ts
  • packages/cli/src/commands/login/index.ts
  • packages/cli/src/commands/login/command.ts
  • packages/cli/src/commands/db/index.ts
  • packages/cli/src/commands/db/command.ts
  • packages/cli/src/commands/db/queryOutput.ts
  • packages/cli/src/commands/db/restore.ts
  • packages/cli/src/commands/status/index.ts
  • packages/cli/src/commands/status/command.ts
  • packages/cli/src/commands/status/problems.ts
  • packages/cli/src/commands/secret/index.ts
  • packages/cli/src/commands/email/index.ts
  • packages/cli/src/commands/email/command.ts
  • packages/cli/src/commands/access/index.ts
  • packages/cli/src/commands/dev/command.ts
  • packages/cli/src/commands/dev/index.ts
  • packages/cli/bin/everyapp.js
  • packages/sdk/src/internal/gatewaySettings.ts
  • packages/perimeter/src/manifest/manifest.ts
  • landing-page/src/lib/source.ts

On this page