You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Four numbers in the agent-facing docs are wrong against the code, and root AGENTS.md predates a contract change its own first recipe depends on
All verified against source 2026-08-15. pnpm docs:drift passes all 13 checks with these present — see the companion guard-gap issue.
Hard errors
location
says
actual
workers/mcp/AGENTS.md:15
"18 of the 124 registrations are gated"
19 of 136 (tool-count.ts: MCP_TOOL_COUNT = 136, MCP_TOOL_ALWAYS_ON = 117)
workers/mcp/CLAUDE.md:83
"31 in index.ts … 13 in storage-tools.ts, and 88 across instance-tools/"
31 + 13 + 92 = 136. The doc's own arithmetic gives 132 and never reaches its own headline
workers/mcp/CLAUDE.md:87,96
"86 instance tools", present tense
86 is historical. contract.test.ts:29 uses it correctly as history ("while 67 of the 86 … lived in one 1871-line file")
workers/mcp/CLAUDE.md:228
"server.json and platform-docs/mcp.md restate it and docs:drift fails when any of the three disagree"
Four statements. The omitted one is store/.well-known/mcp-server.json
README.md:416
biome "packages workers/mcp: the two trees", listing workers/api ~234, store/** ~54, agents/coder 1 as blocked
ci.yml runs packages workers store agents/coder; its own comment says "Every tree is now in." All three landed 2026-08-07
CLAUDE.md:228 is the sharpest: the statement it omits is the one #573's first pass already missed once — scripts/lib/wire-surface.mjs:285 exists precisely because of that miss. The doc reproduces the original bug in prose.
Root AGENTS.md — stale by omission, and standing on a changed contract
Last commit 2026-06-22. Nothing in it is false: every rule, tool name and both flows were traced and still behave as described; dry_run, the exact-confirm rule and mcp_audit_log are all correct. For a two-month-old contract that is a better result than expected.
The problem is what it cannot tell a caller, and one item is a contract change it sits directly on top of: its first recipe opens with list_agents, whose payload is now {"agents":[…]} and no longer a bare array (tool-metadata.ts:344, same for my_instances). A consumer following this file literally breaks.
Also absent, all verified present in code:
tool annotations readOnlyHint/destructiveHint (tool-metadata.ts:98) — these make its own rule "prefer read-only tools" mechanically answerable instead of a judgement call
the server instructions block sent on initialize (index.ts:45)
RUN_HEALTH_STATES — four members incl. ended (work-report.ts:93), and ended is the common answer since runHealth() returns it for any status !== "running"
lastAliveAt (heartbeat) vs lastProgressAt (instruction advance) as different facts; runHealth deliberately reads liveness only (work-report.ts:179)
TOOL_TIERS — four (builtin-tool-policy.ts:57); TOOL_REACHES — three, failing closed at internet (tool-reach.ts:71)
The verdict field lists omit the two fields that answer the questions
workers/mcp/AGENTS.md:62 lists (allowed, scope, disabled, reason, tier, invocableBy); workers/mcp/README.md:~340 lists (allowed, scope, disabled, reason). Both omit mutates and reach.
Neither recommends connector as the reach proxy — that specific wrong advice was removed by #585 and is genuinely gone. But naming scope while omitting mutates leaves the same wrong inference available, and tool-registry.ts:149 says outright that mutates is the field that answers it.
Already correct — leave alone
workers/mcp/README.md's tool table: 136 rows, exact match against registration per the gate; the coding_timeline row is present and accurate.
workers/mcp/AGENTS.md rule 3, "this server does not set isError": still true. registration.ts:114-116 attaches structuredContent: {error: …} rather than letting the SDK's validateToolOutput rejection fire, closing the one path that could produce it.
README.md:331-332,358-359 correctly names the tool table and tool-count.ts as sources.
One documented behaviour narrower than the code
AGENTS.md:36 frames the runtime flow as "when local Playwright is needed". The flow and register_instance_runtime exist and work, but the transport is now the unconditional WebSocket relay and the runner serves coding engines as well as browsers. Narrower framing, not a false claim.
Acceptance criteria
All five numeric errors corrected against their cited source.
Root AGENTS.md states the {"agents":[…]} / {"instances":[…]} payload shape — this is the one that breaks a caller.
Annotations, server instructions, and the mutates / reach / four-value tier and RunHealth vocabularies are documented where a calling agent will read them.
reach and mutates added to both verdict field lists.
The four security mechanisms added to SECURITY.md.
README tool table untouched — it is exact.
pnpm docs:drift and docs:build green. Baseline is green today, so any red is yours.
Verified vs inferred
Verified: every file:line; the per-file .tool( counts; the four-way version statement set from the gate's own output; ci.yml's biome invocation; both flows in root AGENTS.md traced end to end.
Four numbers in the agent-facing docs are wrong against the code, and root
AGENTS.mdpredates a contract change its own first recipe depends onAll verified against source 2026-08-15.
pnpm docs:driftpasses all 13 checks with these present — see the companion guard-gap issue.Hard errors
workers/mcp/AGENTS.md:15tool-count.ts:MCP_TOOL_COUNT = 136,MCP_TOOL_ALWAYS_ON = 117)workers/mcp/CLAUDE.md:83index.ts… 13 instorage-tools.ts, and 88 acrossinstance-tools/"workers/mcp/CLAUDE.md:87,96contract.test.ts:29uses it correctly as history ("while 67 of the 86 … lived in one 1871-line file")workers/mcp/CLAUDE.md:228server.jsonandplatform-docs/mcp.mdrestate it anddocs:driftfails when any of the three disagree"store/.well-known/mcp-server.jsonREADME.md:416packages workers/mcp: the two trees", listingworkers/api~234,store/**~54,agents/coder1 as blockedci.ymlrunspackages workers store agents/coder; its own comment says "Every tree is now in." All three landed 2026-08-07CLAUDE.md:228is the sharpest: the statement it omits is the one #573's first pass already missed once —scripts/lib/wire-surface.mjs:285exists precisely because of that miss. The doc reproduces the original bug in prose.Root
AGENTS.md— stale by omission, and standing on a changed contractLast commit 2026-06-22. Nothing in it is false: every rule, tool name and both flows were traced and still behave as described;
dry_run, the exact-confirmrule andmcp_audit_logare all correct. For a two-month-old contract that is a better result than expected.The problem is what it cannot tell a caller, and one item is a contract change it sits directly on top of: its first recipe opens with
list_agents, whose payload is now{"agents":[…]}and no longer a bare array (tool-metadata.ts:344, same formy_instances). A consumer following this file literally breaks.Also absent, all verified present in code:
readOnlyHint/destructiveHint(tool-metadata.ts:98) — these make its own rule "prefer read-only tools" mechanically answerable instead of a judgement callinstructionsblock sent oninitialize(index.ts:45)RUN_HEALTH_STATES— four members incl.ended(work-report.ts:93), andendedis the common answer sincerunHealth()returns it for anystatus !== "running"lastAliveAt(heartbeat) vslastProgressAt(instruction advance) as different facts;runHealthdeliberately reads liveness only (work-report.ts:179)TOOL_TIERS— four (builtin-tool-policy.ts:57);TOOL_REACHES— three, failing closed atinternet(tool-reach.ts:71)The verdict field lists omit the two fields that answer the questions
workers/mcp/AGENTS.md:62lists(allowed, scope, disabled, reason, tier, invocableBy);workers/mcp/README.md:~340lists(allowed, scope, disabled, reason). Both omitmutatesandreach.Neither recommends
connectoras the reach proxy — that specific wrong advice was removed by #585 and is genuinely gone. But namingscopewhile omittingmutatesleaves the same wrong inference available, andtool-registry.ts:149says outright thatmutatesis the field that answers it.Already correct — leave alone
workers/mcp/README.md's tool table: 136 rows, exact match against registration per the gate; thecoding_timelinerow is present and accurate.workers/mcp/AGENTS.mdrule 3, "this server does not setisError": still true.registration.ts:114-116attachesstructuredContent: {error: …}rather than letting the SDK'svalidateToolOutputrejection fire, closing the one path that could produce it.SECURITY.md:38's write-consent gate. Missing (additions, not corrections): themutates/scopedistinction, fail-closedreach, the append-only surface lock (The surface lock can be rewritten in place, so a silent surface change is still shippable #576), the status-domain guard.README.md:331-332,358-359correctly names the tool table andtool-count.tsas sources.One documented behaviour narrower than the code
AGENTS.md:36frames the runtime flow as "when local Playwright is needed". The flow andregister_instance_runtimeexist and work, but the transport is now the unconditional WebSocket relay and the runner serves coding engines as well as browsers. Narrower framing, not a false claim.Acceptance criteria
AGENTS.mdstates the{"agents":[…]}/{"instances":[…]}payload shape — this is the one that breaks a caller.instructions, and themutates/reach/ four-valuetierandRunHealthvocabularies are documented where a calling agent will read them.reachandmutatesadded to both verdict field lists.SECURITY.md.pnpm docs:driftanddocs:buildgreen. Baseline is green today, so any red is yours.Verified vs inferred
file:line; the per-file.tool(counts; the four-way version statement set from the gate's own output;ci.yml's biome invocation; both flows in rootAGENTS.mdtraced end to end.