Skip to content

Agent CLI driver matrix — verified, not from docs

Every row was read from --help and then confirmed by a live probe that checked what landed on the board. Versions matter; re-run mooting doctor after any CLI upgrade rather than trusting this file.

CLI version headless resume by id prompt via per-run MCP injection read-only mode
Claude Code 2.1.250 -p/--print --session-id/--resume (our UUID) argv --mcp-config + --strict-mcp-config --allowedTools allowlist
Codex 0.149.0 codex exec yes, but see below stdin (-) no — mooting install —
Copilot 1.0.81 -p + --allow-all-tools -r/--resume=<id> argv --additional-mcp-config --deny-tool
Antigravity agy 1.1.20 -p/--print --conversation <id> argv no — mooting install --mode plan

Four traps, each of which looks like something else

These cost real time to find. Every one presents as a different problem than it is.

1. Windows .CMD shims cannot carry a multi-line argument

codex and some other CLIs install as npm batch shims. shutil.which resolves codex.CMD, and CreateProcess runs a .CMD through cmd.exe — which cannot pass an argument containing newlines. The prompt breaks at the first line ending and every flag after it disappears.

Council prompts are always multi-line markdown, so this is not an edge case; it is the normal case. And the symptom is not a quoting error:

codex exec "<multi-line prompt>" --approve-for-me
  → header prints  approval: never          (the flag never arrived)
  → MCP tool call requires approval, but approval policy is never

That reads as "the MCP server is misconfigured", and sends you to debug config.toml, -c syntax, and plugins — none of which are involved. Test: the same call with a single-line prompt works. Fix: send the prompt on stdin.

2. codex exec defaults to refusing every MCP call

Its default approval policy is never, which does not mean "don't ask, just run it". It means refuse. The server is loaded, the tool is dispatched, and the call is denied. --approve-for-me is required, and it is mutually exclusive with --sandbox.

3. --json silently defeats --approve-for-me

With both flags the policy reverts to never and every MCP call fails again, same misleading message. Codex prints its session id in the plain-text header anyway.

4. Codex defers MCP tools out of the initial tool list

tool_search_always_defer_mcp_tools is on. Ask codex to "list the tools containing mooting" and it answers NONE while being perfectly able to call them. So "can you see it?" is not a valid health check — only "call it, and did it land?" is. mooting doctor's probe prompt says so explicitly, because an earlier version of it offered "reply NO-MOOTING-TOOLS if you can't see the tool" and codex, truthfully, took that exit every time.

Related noise: a github MCP server that is Not logged in prints rmcp worker quit with fatal: ... AuthRequired on every codex start. It is alarming and irrelevant — other servers load fine alongside it.

Session continuity is optional, and two seats decline it

Claude resumes cleanly by a UUID we choose. The others have reasons not to:

  • Codex — resume and stdin are mutually exclusive. codex exec resume <id> demands a literal positional prompt and will not take -. Since multi-line prompts must go through stdin (trap 1), resume loses.
  • Antigravity — --conversation <id> works, but a resumed seat carries its whole history: a probe conversation reached 132k input tokens, and one resumed turn took 800 seconds against 13 for a fresh one.

This costs nothing, because the board is the shared memory. A stateless seat rebuilds context from the record each turn and cannot drift from what was actually said. stateful = False is a supported mode, not a degraded one.

Blast radius

v0 seats deliberate; they do not edit files. Each adapter asks its CLI for the narrowest surface it offers, and they are not equally strong — Claude's --strict-mcp-config plus an allowlist is tightest; Antigravity's plan mode is genuinely read-only; Copilot must be given --allow-all-tools for -p at all, so it is narrowed by denial instead. mooting doctor verifies reachability empirically rather than trusting that a flag did what its name says.

Machine-local quirks belong in the seat, not the driver

mooting agents add <name> <kind> --arg=... appends argv to every wake for that seat. Use it for one machine's problems — a broken plugin to switch off, a flag a newer build needs — so the adapters stay general instead of accumulating one person's environment.

What each CLI reports about cost

Only the vendor knows what came off your subscription, so /usage shows what the CLI itself said and nothing where it said nothing. A seat that reports nothing is unmeasured, not free.

seat reports tokens and cost
claude yes — runs with --output-format json, which carries both
agy runs with --output-format json; whether it carries the numbers is measured, not assumed
codex no — --json silently defeats --approve-for-me, so it is deliberately not passed
copilot no structured output requested
gemini no structured output requested

mooting doctor answers it for your machine rather than from this table: it already spends real turns on each seat, and now says what they reported.

Which seats can actually do work

A work seat has to reach the git worktree it is given and commit to it. Both are things a vendor decides, and neither is visible from the flag names, so this table says what was probed rather than what the documentation implies.

All five were probed on 2026-09-06, each against a real repository with a staged change, using the flags this project actually passes.

seat reaches the worktree can commit what it took
claude yes yes Bash(git commit:*) named in --allowedTools; acceptEdits alone is not enough
codex yes yes nothing — --approve-for-me already covers it
copilot yes yes nothing, because nothing narrows it. That is G1's concern rather than this one
agy only with --add-dir yes the directory has to be handed to it; the mode was never the problem
gemini — — could not run: IneligibleTierError -- this client is no longer supported for Gemini Code Assist for individuals, which points at Antigravity

Two adapters needed changing, two were already right, and no two failed the same way. Nothing in the flag names carried any of it.

Two things that cost real turns to learn, both invisible from outside:

--permission-mode acceptEdits approves file edits and only file edits. git commit is an ordinary shell call, so it needs an approval a non-interactive run cannot give. A seat then does the work correctly and leaves it uncommitted, which the loop can only read as no work at all.

agy ignores the process working directory for shell commands. Asked where it was, it answered ~\.geminintigravity-clirain\<uuid>\scratch and fatal: not a git repository. --add-dir is what puts the worktree in scope.