Planning bug: a `coding_agent` card carried a criterion only a BROWSER SESSION can discharge — minting a `CLI_TOKEN_GRANT` token is cookie-session-only by design, and no gate noticed
Opened by Zhu Yue ·
Found running motir run MOTIR-3795 (runbook parent-run), 2026-08-28, at the story's first child.
What happened
MOTIR-3796 (type: verification, executor: coding_agent, 1 point / 20 min) carries acceptance criterion 3:
The reading is taken with a token holding no more than
CLI_TOKEN_GRANT's permissions, so it verifies the door a dispatched agent will actually come through, not the one a workspace PAT can.
That criterion cannot be performed by a coding agent, and the reason is a deliberate security property rather than a gap. Both doors that mint such a token are cookie-session-only, and both say so in their own headers on origin/main:
app/api/me/api-tokens/route.ts— "Deliberately COOKIE-SESSION ONLY (never PAT-authed): the PAT is for agents, but the surface that MINTS a PAT must not be reachable WITH a PAT, so a leaked token cannot mint more tokens."app/api/cli/device/approve/route.ts— "COOKIE-SESSION ONLY, and that is the whole security model … this route is never bearer-reachable — a PAT cannot mint more PATs." Its precondition is a signed-inGET /api/auth/device?user_code=…in a browser.
So discharging AC 3 requires a signed-in human at app.motir.co. run.md's never-ask rule forbids a run from asking for one, and _shared.md records that the account credentials are not stored in the corpus. The card is therefore unfinishable as written by the executor it names.
Why no gate caught it
This is lesson 30's shape — "Every external-service or secret/dashboard prerequisite a code subtask needs is its OWN planned subtask (type: manual/human, no PR, marked done on the user's confirmation), wired via depends_on so it's visible at PLAN time and never ambushes a motir run" — and every automatic signal was green:
readiness.ready: true,openBlockers: [].validate_work_itemreturns noshapeadvisory for this class.likely-over-gate-sizingreads the numbers,likely-repo-straddlereads paths againsttargetRepo,likely-ordering-violationmatches post-merge phrases. None of them asks whether the card's ownexecutorcan perform its own criteria.- The criterion looks like an ordinary tightening of a measurement, not like console work. It names no dashboard, no secret and no provider — the vocabulary lesson 30 taught a reader to spot.
That last point is the transferable half. The manual-prerequisite gate is written around external-SaaS vocabulary, and this criterion is about our own product's credential model — so it reads as in-house and reachable while being exactly as human-gated as a Stripe dashboard.
What the run did instead (recorded on MOTIR-3796, not hidden)
The round-trip was taken with the workspace PAT and the residue was written down. What AC 3 was actually asking — does the door a dispatched agent comes through open? — is already mechanised on origin/main:
lib/mcp/toolPermissions.ts:193→publish_design_result: 'work_item:edit', a keyCLI_TOKEN_GRANTalready carries and which the tool does not widen.tests/mcp/publishDesignResultTransport.test.ts:262→ "a token minted onCLI_TOKEN_GRANTpublishes a design result end to end", with the grant read from the exported constant, so a later narrowing fails a test rather than silently changing what a sandboxed agent may do.
That runs on every pull request, which is stronger than one manual reading — but against a test database, not against the deployment. The deployment-side half is what stayed unmeasured.
Acceptance criteria
plan-rules/kind-leaf-deepen.md's manual-prerequisite limb names the IN-HOUSE case, not only external SaaS: a criterion is human-gated when the surface that performs it is cookie-session-only, whoever owns it — the test is who can hold the credential, not whose product it is. The added text names the two Motir routes above as the standing instance.- The addition is an ADD, never a re-wording of the surrounding rule (
motir-meta's corpus convention), andprompts/plan-rules/MANIFEST.mdstill routes to the limb it lands in. - The check is stated so a plan pass can run it in one question: for each acceptance criterion, could the card's own
executorvalue perform it? — withcoding_agent+ a browser-session-only surface being the fail. - No claim that
validate_work_itemshould detect it. The advisory tier reads a card's text against the tree and the repository; can this executor do this is not a question it has the inputs for. Say so, so the next reader does not file the same finding as a missing detector.
Context refs
- MOTIR-3796 — the card, and the comment recording the full disposition and the evidence id (
cmtdlo5mx00dnhvphd5yy3jey). motir-core/app/api/me/api-tokens/route.ts·app/api/cli/device/approve/route.ts— the two cookie-session-only mint doors.motir-core/lib/mcp/toolPermissions.ts·tests/mcp/publishDesignResultTransport.test.ts— the constant and the guard that already answer AC 3's question in CI._shared.md§ Mistakes to avoid #30 — the lesson this is a new instance of.
Discussion
No comments yet.
Adding to this discussion signs you in on app.motir.co and brings you back to this request.