Checks and diagnostics
Before you ship, ask:
$ pnpm infra check example/guestbook/app.ts✓ guestbook: no problems found (app, types, bundle)That’s what you want to see. Here’s what you’d rather see now than in
production. The fixture
test/fixtures/diagnostics
has one of each problem the offline checks find:
$ pnpm infra check test/fixtures/diagnostics/app.tserror used-undeclared test/fixtures/diagnostics/api.ts:16:133 the Worker uses Db, which it doesn't bind fix: bind it: .bind(…, Db)
error compatibility-date-future test/fixtures/diagnostics/api.ts:16:64 the Worker api's compatibility date, 2099-01-01, is after today's, 2026-10-17 fix: set compatibilityDate: '2026-10-17', or an earlier date
error cron-invalid test/fixtures/diagnostics/api.ts:16:98 the Worker api's cron "61 * * * *": its minute has "61"; it takes 0 to 59 fix: write it as five fields, such as '*/5 * * * *' for every five minutes
error name-too-long test/fixtures/diagnostics/rooms.ts:13:28 the Worker rooms-with-a-name-long-enough is named diagnostics-fixture-with-long-name-rooms-with-a-name-long-enough in the account, 64 characters; a Worker's name is at most 63 fix: shorten the app's name (diagnostics-fixture-with-long-name) or the Worker's (rooms-with-a-name-long-enough) by 1 character
warning queue-no-consumer test/fixtures/diagnostics/api.ts:16:28 the queue jobs has producers (the Worker api) and no consumer, so its messages wait until they expire fix: consume it in one Worker: queues: [Jobs.consume(handler)]
warning declared-unused test/fixtures/diagnostics/idle.ts:7:39 the Worker binds Db, and its code never uses it fix: drop Db from its .bind(…), or use it
warning handler-missing test/fixtures/diagnostics/idle.ts:7:28 the Worker idle has no handlers, so it answers nothing fix: give it a fetch, an HTTP app, a cron, a queue, a contract, a Durable Object, a workflow or a tail handler, or remove it from the app
warning migration-destructive test/fixtures/diagnostics/rooms.ts:10:65 a migration of the Durable Object ledger drops data: DROP TABLE entries fix: make sure it's meant: it runs once on every object, the first time each starts on the new version
diagnostics-fixture-with-long-name: 4 errors, 4 warnings (app, types, bundle); `infra explain <code>` says moreEvery problem has the same shape: how bad it is, a stable code, the file, line and column, what’s wrong in a sentence, and the fix. It exits 1 when there’s an error, so it fits in CI as it is. That run took about five seconds.
What it checks
Section titled “What it checks”Offline, with no account and no token:
- Types. The TypeScript checker, on the app’s project (the nearest
tsconfig.json). forge-effect’s own compile errors get their own codes: the compiler’s'missing bindings'isused-undeclared, and so on. Anything else TypeScript says istype-error. In the same run, it asks the checker which resources each Worker binds but never uses (declared-unused): a Worker’s type records what its code needs, so this falls out for free. - Declarations. Names over Cloudflare’s limits, a compatibility date in the future, a cron Cloudflare won’t take, a Worker with no handlers, a queue no Worker consumes, a migration that drops data, a route that isn’t one, a variable its schema refuses, and the app’s own mistakes (a resource declared twice, a contract no Worker implements, a Worker importing another’s module), each at the declaration.
- Bundles. Each Worker bundled as it would deploy: a failure, and its size against Cloudflare’s limits (10 MB gzipped; over 3 MB, the free plan’s, is a warning, as the check can’t know your plan), and bindings ship can’t deploy yet.
--no-types and --no-bundles skip those two, when you want it fast.
--env <name> checks an environment’s values (a variable chosen by a
function, say, or routes).
Against your account
Section titled “Against your account”--account adds what plan would read, as diagnostics. It needs
CLOUDFLARE_API_TOKEN, and reads only.
- names taken by something that isn’t the app’s (
name-taken), - resources deleted outside the tool, and Workers running a version it
didn’t deploy (
drift), - orphans waiting for
prune(orphaned), - migrations the next ship applies (
migration-pending), - contract changes that would break deployed callers,
- secrets declared with no value (
secret-missing), with the command that sets one, - Durable Object classes that change, so the ship needs
--atomic(exports-changed), - routes and domains in no zone of the account (
zone-missing), or serving another Worker (route-taken), - what your plan limits, such as cron triggers (
plan-limit), - and a call the token isn’t allowed to make (
token-scope-missing).
infra explain
Section titled “infra explain”Each code means one thing, always. Ask about one:
$ pnpm infra explain used-undeclaredused-undeclared (error): the code uses a resource or contract the Worker does not bind
A handler, or a Layer, uses a resource (a KV namespace, a D1 database, a queue, an object contract, another Worker's contract) that the Worker doesn't bind, so it would fail at runtime with a missing binding. Bind it: `.bind(..., Db)`. The compiler reports it as `'missing bindings'`.With no code, it lists them all, with their severity (trimmed here; there are 57):
$ pnpm infra explainusage: infra explain <code>; the codes: used-undeclared error the code uses a resource or contract the Worker does not bind object-service-outside error a Durable Object's own service is used outside an object contract-unimplemented error a Worker calls a contract no Worker of the app implements … declared-unused warning a Worker binds a resource its code never uses … name-taken error a resource's name exists in the account, and isn't this app's secret-missing error a Worker declares a secret that has no value … plan-stale error a saved plan is out of date orphaned info a resource is no longer declared, and kept until prune …For agents and scripts: --json
Section titled “For agents and scripts: --json”--json prints the same as a report, for a program, or an AI agent, to
read:
$ pnpm infra check test/fixtures/diagnostics/app.ts --json --no-types --no-bundles{ "version": 1, "app": "diagnostics-fixture-with-long-name", "env": "production", "checks": [ "app" ], "errors": 3, "warnings": 3, "diagnostics": [ { "code": "compatibility-date-future", "severity": "error", "message": "the Worker api's compatibility date, 2099-01-01, is after today's, 2026-10-17", "source": "app", "location": { "file": "test/fixtures/diagnostics/api.ts", "line": 16, "column": 64, "worker": "api" }, "fix": { "description": "set compatibilityDate: '2026-10-17', or an earlier date" } }, …version: 1 is a promise: fields are added, never renamed. A fix can
carry a command too, such as the infra secret put that sets a missing
secret.
Two more commands report the same way:
-
infra graph --json: what the app declares, read with no requests. Each Worker’s name in the account, handlers, resources with their bindings and names, crons, routes, Tail Workers, and the order they deploy in. It’s how an agent checks that the app it wrote is the app the tool sees:$ pnpm infra graph example/guestbook/app.ts --json{"version": 1,"app": "guestbook","order": ["guestbook"],"workers": [{"name": "guestbook","script": "guestbook-guestbook","compatibilityDate": "2026-10-01","handlers": ["fetch"],…"resources": [{"kind": "d1","name": "book","binding": "BOOK","physicalName": "guestbook-book",… -
infra plan --json: the plan, with check’s findings in it, each resource’s and Worker’s status, ship’s plan for each Worker, and contract changes.
Where the checks run
Section titled “Where the checks run”You don’t have to remember to run them:
- In your editor, the type errors appear as you type, since they’re the compiler’s.
infra planruns the offline checks (all but the bundles, which the plan makes itself) and prints what they find above the plan.infra shipruns them too, and stops on an error before it touches your account.--no-checkskips them.
Checks only ever say what’s wrong. Your declarations are the source of truth, and a check never edits them.
Good to know
Section titled “Good to know”declared-unusedis a warning, not an error: binding something you don’t use costs nothing at runtime, but it’s often a sign of a leftover. Files (Assets.Directory) are exempt, since Cloudflare serves them without your code.- The type check covers the whole project the app is in, so an error in a test file shows up too.
- There’s no editor plugin for the non-type checks yet: run
infra check.