BUG — the sandbox smoke's stub server still speaks MCP only, so every image smoke test 405s the moment the CLI moved to `/api/v1`
packages/cli/sandbox/smoke/stub-server.mjs is the zero-dependency server the sandbox IMAGE smoke tests run the built motir binary against. It speaks MCP JSON-RPC and nothing else — a TOOLS table dispatching whoami / list_ready / next_ready / transition_status / dispatch_prompt / mark_integrated over POST, plus the two /api/cli/device/* routes.
Since MOTIR-2214 (11.5.6) the CLI makes no MCP calls at all. Every read is now a GET /api/v1/..., and this server's first branch is:
if (req.method === 'GET') {
res.writeHead(405, ...).end('{"error":"no sse stream"}');
return;
}
So the very first request of every smoke run gets a 405. Nothing after it can pass.
⚠️ THIS TURNS THE PARENT PR RED
ci.yml's sandbox job calls sandbox-images.yml on every pull request, skipping only seed/ · design/ · docs/ branches. The story's branch is none of those, so the image lane runs and fails. This is a merge blocker for Story 11.5, not a deferred cleanup.
What to build
- Serve
/api/v1from the stub. Route by method + path pattern (the routes the smoke loop actually drives):GET /api/v1/me,GET /api/v1/workspaces,GET /api/v1/projects,GET /api/v1/projects/{key}/ready,GET /api/v1/work-items/{key},GET /api/v1/work-items/{key}/dispatch-prompt,POST /api/v1/work-items/{key}/transitions,POST /api/v1/work-items/{key}/integration,POST /api/v1/sessions/complete. Drive the exact set from what the smoke scripts exercise rather than from this list — the list is a starting point, not a contract. - Keep the recording.
assert-run.mjsasserts the recorded call SEQUENCE, which is the thing an exit code cannot tell you. The recorded shape becomesMETHOD pathrather than a tool name; update the assertions with it, and keep the sequence check — it is what proves the loop's ORDER, not merely its outcome. - Keep it zero-dependency. It runs under the bare
nodein the image; no imports beyondnode:*. - Decide the fate of the MCP half. The device routes stay (login is unchanged). The
TOOLStable has no caller once (1) lands — delete it rather than leave a second protocol nothing drives, on the same principle 11.5.6 applied to the client. - The response bodies must be REAL v1 shapes. The CLI validates every response against its generated Ajv validator, so a hand-approximated body is rejected by the client before any renderer sees it — this stub cannot fake loosely the way the MCP one could.
Scope BOUNDARY
Ends at the stub server, the smoke scripts' assertions, and sandbox/README.md's description of them. It does NOT change the CLI, the image, the Dockerfile, the profile matrix or the device-grant flow. It does NOT touch the CodeGraph MCP wiring in the same README — that is the AGENT's MCP server for code search and is entirely unrelated to the CLI's transport.
Acceptance criteria
packages/cli/sandbox/smoke/run.shpasses end to end against the built image, with the CLI making only/api/v1and/api/cli/device/*requests.assert-run.mjsstill asserts an ORDERED sequence, now over v1 requests, and is proven to fail when the order is wrong.- Every scripted response body validates against the generated client's Ajv validator for its operation — asserted, not assumed, since the client rejects a body that does not.
- No
tools/call/tools/listhandling remains instub-server.mjs. packages/cli/sandbox/README.md's smoke section describes what the stub now serves; its CodeGraph MCP sections are untouched.- The
Sandbox imagesjob is green on the story's pull request.
Context refs
packages/cli/sandbox/smoke/stub-server.mjs— the file; note theGET → 405branch at the top of the request handler is the immediate failure.packages/cli/sandbox/smoke/{loop,failure,login,readonly-login}-smoke.sh— the four scripts that start it, and the source of the operation set to serve.packages/cli/sandbox/smoke/assert-run.mjs— the sequence assertions to re-point.tests/helpers/mcpHttpServer.ts— a path-pattern matcher with literal-beats-dynamic precedence already exists there; the shape is worth copying, but this file cannot import it (zero-dependency, runs in the image)..github/workflows/ci.yml§sandbox— why this is a PR blocker rather than a nightly one.- Caused by: MOTIR-2214 · Found by: MOTIR-2217.
- Story: MOTIR-1855.