Cloud fix executor
MAINARK_EXECUTOR=cursor-cloud runs the fix leg of the review loop in Cursor
Cloud, on the PR's own branch.
Why
Every other fix executor runs locally. local and cursor shell out to the
Cursor CLI (cursor agent); codex needs OPENAI_API_KEY. A headless host that
dispatches implement runs to Cursor Cloud has neither.
Observed on the first live run on the Hetzner host: implement (Cursor Cloud) and
review (Claude) both worked, and the fix phase printed
Error: required command not found: cursor. It was harmless only because the
review passed on round 1. A FAIL verdict could have been reviewed but never
fixed, and the loop would have spent all its rounds re-reviewing unchanged code.
How
The executor reads .reviews/pr-N.json, decides whether there is anything to
fix, and if so starts a Cursor Cloud agent with:
| Option | Value | Why |
|---|---|---|
startingRef | PR head branch | Work on the PR's code, not main |
prUrl | the PR | Attach to the existing PR |
workOnCurrentBranch | true | Push to the head branch (ADR-002) |
autoCreatePR | false | Never open a second PR |
It waits for the run, then requires the PR head SHA to have moved. A finished agent that pushed nothing is a failure, because otherwise the loop would go back to review identical code.
Cost guards
-
Worktree sync. After a confirmed push the executor fast-forwards the local PR worktree (
merge --ff-only) to the pushed SHA. The next review round validates there; without the sync it re-tests the pre-fix code, fails, and dispatches again. A diverged worktree fails the round rather than being reset. -
Branch check, before paying. The workspace must be checked out on the PR's head branch before a run is dispatched, and again before syncing. When no PR worktree exists, review falls back to the main clone; a fast-forward there would move the operator's branch to the PR head.
-
No-push guard.
.reviews/pr-N.cloud-fix.jsonrecords the head the last cloud fix worked on and whether it produced a usable push. If it did not on the same head, the executor refuses another paid run. "Did not" covers every way a created run can fall short — finished unchanged, ended in error or cancelled, timed out, push unconfirmed, or pushed but the worktree sync failed (recorded against the new head). A create call that is rejected outright records nothing, since it cost nothing and may be transient. A new head re-enables it; deleting the marker forces a retry. The review loop has no brake of its own and will otherwise dispatch once per round. -
A lost stream is confirmed, not believed. If
wait()throws, or resolves with anything other thanfinished, the executor asks the API for the run's real state and keeps polling while it is still running. At the deadline, or if the state cannot be read at all, the run is cancelled — never left running unmonitored. Found live: the stream reportederror: Run stream is no longer availablewhile the agent was still working and could have pushed after the loop had reached consensus. -
SHOULD FIX alone does not start a paid run. A PASS verdict whose only items are suggestions no longer dispatches a cloud fix (live: $0.41 on "add database indexes" after approval). Set
MAINARK_CLOUD_FIX_SHOULD_FIX=1to opt back in. A fix already running for a failure still triages SHOULD FIX items.
The first two were found on the first live run: one real fix, then four paid runs against an already-fixed branch before the round cap.
Differences from the local fix
- Findings are inlined in the brief. The VM clones the repo and cannot read
.reviews/on the host. - The agent commits and pushes. Locally, Mainark commits after the run.
- Same run/skip gate as
cli/cursor-fix-pr-review.sh. The review loop calls the fix phase even after a PASS; without the gate this would start a paid agent on every approved PR.
Configuration
| Env | Default | |
|---|---|---|
MAINARK_EXECUTOR | auto | Set to cursor-cloud |
CURSOR_API_KEY, REPO_URL | — | Same as implement |
MAINARK_CLOUD_FIX_TIMEOUT_MS | 45 min | Run is cancelled on timeout |
MAINARK_CLOUD_FIX_SHOULD_FIX | off | Let SHOULD FIX items alone start a paid run |
MAINARK_VALIDATE_CMD | pnpm mainark:validate | Named in the brief |
Set MAINARK_EXECUTOR, not MAINARK_FIX_EXECUTOR: the bash pipeline derives the
latter from the former and overwrites a directly set value.
Requires the TypeScript fix pipeline (the default). The legacy bash router
(MAINARK_FIX_TS=0) refuses cursor-cloud with an explicit error.