Bugfix spec: the cursor adapter passes the model as -m, an option cursor-agent does not have
Summary
Every critic declared harness: cursor with a model: has never run. The adapter spells the model -m; cursor-agent has only --model, so the process dies on error: unknown option '-m' before it reads the prompt. The same critic with no model: runs fine — which is why the defect reads as missing review rounds rather than as a broken configuration: the-loop critic list shows the entry as runnable, critic policy still asks for its rounds, and each round comes back unavailable with a CLI error nobody attributed to the-loop.
Reported against 15.0.0 by a collaborator running cursor-agent 2026.09.10-fd3934a. Ticket: issue-360.
flowchart TD
A["critics[]: harness: cursor, model: gpt-5.6-sol"] --> B["critics.resolve_invocation"]
B --> C["HarnessAdapter.oneshot_argv(prompt, model)"]
C --> D["base.py:171 — argv + [self.model_flag, model]"]
D --> E["CursorAgentAdapter.model_flag = '-m'"]
E --> F["cursor-agent -p … --output-format json -m gpt-5.6-sol"]
F --> G["error: unknown option '-m'<br/>exit before the prompt is read"]
G --> H["the round is recorded 'unavailable'<br/>— and an unavailable round does NOT count<br/>toward reviews.criticReviewCount"]
H --> I["the review bar is never met on cursor,<br/>and the roster still looks healthy"]The last step is what makes this worth a spec rather than a one-character commit: an unavailable round is deliberately not counted (reference/reviewing.md), so a wrong flag does not degrade the review loop loudly — it removes a critic from it quietly.
Steps to reproduce
Declare a cursor critic with a model in the CLI config:
yamlcritics: - name: cursor-gpt56 harness: cursor model: gpt-5.6-sol-highthe-loop critic run --name cursor-gpt56 --prompt "review this".The critic exits non-zero with
error: unknown option '-m'on stderr.
Without the CLI, the argv the-loop builds shows it on its own:
$ uv run --project cli python -c "from the_loop.harness import CursorAgentAdapter as C; \
print(C().oneshot_argv('review this', model='gpt-5.6-sol'))"
['-p', 'review this', '--output-format', 'json', '-m', 'gpt-5.6-sol']The reporter's live proof, on cursor-agent 2026.09.10-fd3934a:
$ cursor-agent -p "say OK" --output-format json -m auto --force
error: unknown option '-m'
$ cursor-agent -p "reply with exactly: OK" --output-format json --model auto --force
{"type":"result","subtype":"success","is_error":false,"result":"OK", ...}Expected vs actual
- Expected: a critic on a built-in harness runs with the model it declares, because the adapter passes it in a spelling that harness's own
--helplists. - Actual:
cursor-agentrejects the argv outright. The model never reaches a model; the round is lost, and the operator's only workaround is to leavemodel:empty and put["--model", "<name>"]inargs— which works, but then the review attribution prefix reads[cursor/default]and the paper trail no longer names what reviewed the diff.
Root cause (confirmed)
cli/the_loop/harness/cursor_agent.py:22 declares model_flag = "-m", and cursor-agent has no such option. HarnessAdapter.oneshot_argv (harness/base.py:171-172) appends [self.model_flag, model] whenever a model is resolved, so the bad flag reaches the argv of every cursor invocation that carries one — critics today and the model probe with them.
The probe is the second victim, and the more corrosive one. the-loop models check builds the same one-shot argv (modelprobe.py:158) and reads a non-zero exit as refused (modelprobe.py:165-174) — the harness said no. cursor-agent exits non-zero on -m before it has an opinion about any model, so every declared model comes back refused on cursor, is cached as such, and is then withheld from the phase-selection checklist so a human cannot pick it. A parse error about the-loop's own flag is recorded as the vendor refusing the operator's model.
Interactive dispatch is not affected: cursor-agent has no pre-assignable session id, so the adapter's interactive_* methods raise UnsupportedRunnerError and no cursor session is ever spawned.
It was never measured. -m is a plausible short form — claude has --model, many CLIs alias it — and the repository's own tests asserted the adapter's constant back to itself (test_critics.py:222, test_modelchoice.py:128 both pinned -m), so the suite agreed with the bug rather than catching it. The only test that ran a real one-shot argv end to end (test_oneshot_argv_without_a_model_is_the_plain_one_shot_run) passed no model, and so walked past the defect.
Requirements
Requirement 1 — a cursor critic with a model runs
User story: as an operator, I want a critic declared harness: cursor with a model: to actually run on that model, so that the review rounds I configured happen and the attribution prefix names what reviewed the diff.
Acceptance criteria (EARS)
- WHEN a model is resolved for the cursor harness THEN the system SHALL pass it as
--model <name>, the spellingcursor-agent --helplists. - WHEN a critic declares
harness: cursorandmodel: <name>and nocommandTHENresolve_invocationSHALL produce["cursor-agent", "-p", <prompt>, "--output-format", "json", "--model", <name>]. - WHEN no model is resolved THEN the cursor argv SHALL be unchanged from today's — the fix SHALL NOT introduce a flag into a run that declares no model.
- The fix SHALL include a regression test that fails before the fix and passes after.
Requirement 2 — the flag is a measured fact, not adapter folklore
User story: as a maintainer adding the next adapter, I want the rule that a model_flag is a spelling the harness's own --help carries to be written down where the capability is described, so the next adapter is not guessed the same way.
Acceptance criteria (EARS)
- The
review-loopcapability doc SHALL state that an adapter'smodel_flagis a spelling that harness's--helplists, SHALL prefer the long form where a CLI offers both, and SHALL name the current value for each shipped adapter. - The capability doc's History table SHALL carry a row tracing this behaviour to issue-360.
Security considerations
No new attack surface. The change is one string constant on an argv the-loop already built, handed to a binary it already spawned, with the operator-declared model name it already passed.
| Abuse case | Boundary | How it fails closed |
|---|---|---|
AC1 — a model name smuggles a second option into the cursor argv (--model, then ; rm -rf) | the critic spawn | Unchanged: run_critic spawns a list argv with no shell, and the name is one list element. The long flag consumes exactly one following argument, as the short one was meant to; nothing about that changes here. |
| AC2 — the long form makes the-loop accept a model it previously rejected | model resolution | There is no such gate to weaken: model_args never validated a name, and modelprobe's verdicts are measured per harness. A name that cursor refuses still comes back refused — measured now through a flag the CLI parses instead of one it rejects before it can refuse anything. |
| AC3 — the fix silently changes what a claude critic runs | the claude adapter | Untouched: ClaudeCodeAdapter.model_flag was already --model, and its argv assertion is unchanged in the suite. |
The bug itself is availability-relevant in the direction that matters for a review harness: it removed review rounds while reporting a healthy roster. Restoring them restores a control rather than relaxing one.
Out of scope
- Probing a harness's flags.
the-loop models checkprobes whether a model runs; it does not parse--helpto verify the-loop's own flag spellings. Making it do so is a real idea and a different work item — it needs the binary present, which neither CI nor this repository's test suite has. docs/specs/issue-358/design.md, which names-mas cursor's flag in an answer to the owner's question. Locked specs are the record of what was decided and believed at the time; they are not edited after the fact. The living statement moves to the capability doc (R2), which is where a reader is meant to look for current behaviour.- An effort flag for cursor. Still none (
_EFFORT_ARGSstays empty, issue-358). - The
argsworkaround in the ticket. It keeps working —argsis appended verbatim and always was.
Open questions
None. The reporter measured the CLI, and the fix is the spelling they measured.