cross-model-recovery.md
38 lines · skills/ce-code-review/references/cross-model-recovery.md at 3449230
Most marks come from matching words in the file, so treat them as pointers.
Cross-Model Adversarial Pass: recovery branches
Read this only from the branch that sends you here: Stage 3d's no-job-id start failure (references/select-and-route.md), or the fold-in read in references/cross-model-review.md Step 5 when the runner's exit code or the job's terminal state says the peer produced no usable artifact. A run whose peer started and folded never opens this file. Every branch below decides one thing: whether the adversarial lens is still covered, and by what.
Outcomes that need a decision
- Dispatch-infrastructure failure — the runner or worker itself crashed: a non-zero exit before any job starts, a preflight/detach failure, or an unresolved
$SKILL_DIR/script path. This is different from the conditions-not-met skips above, where no dispatch was attempted, so do not fold it into the clean not-run bucket on the first error. The two failure shapes recover at different points. A no-job-id preflight failure (exit before any job id, unresolved$SKILL_DIR) is recovered entirely at Stage 3d's no-job branch, before the local roster is final. That is the only point where re-running the start can still recover cross-model corroboration and, failing that, cleanly fall back to the in-process reviewer, which then covers the lens; only corroboration is lost. Do not re-attempt that case here at fold-in: Stage 4 may already have dispatched the in-processadversarial-reviewer, so a fold-in peer re-run would put both on the same brief, and only one of them may run it. This step handles only the job-id-returned-then-failed crash; its failed job is reaped here and the in-process reviewer is already gone. For it, re-run the same resolved fixed route by hand, holding the target and model, thegit diff <base-ref>read scope, and the adversarial persona brief fixed. Keep retrying while each failure is a new, plausibly recoverable one and the shared peer deadline holds. This is a same-route retry, deliberately distinct from the quota rule below, which requires a newly disclosed route. Stop once a failure repeats or the deadline is spent. The hand recovery is then the adversarial lens's only cover, so the Coverage line must report the adversarial lens as degraded, not merely cross-model corroboration lost. A hand recovery may not substitute a different target or provider, widen the read scope, or relax the read-only trust boundary; those make the recovered peer untrustworthy, not merely unavailable. - Transport unavailable before egress —
out.logcarriestransport unavailable (pre-egress, <scope>): <reason>. The peer did not run and no reviewed content reached the provider, so this is never "peer ran, no usable output"; Coverage reports the cross-model peer as not run with that reason. Scopeshared(Node,npx, or the npm fetch of acpx) fails every route the same way, so no replacement peer can cover the lens: take step 2 of the did-not-run fallback. Scoperoute(that route's CLI is missing, an acpx config file replaces its launch command, or the adapter rejected the model, with the available models listed) affects only that route. A rejected model follows the refused-model rule below; any other route reason runs the did-not-run fallback, which allows its one replacement. - Ran but produced no usable output — the job reached
done(or any terminal state) yet noadversarial-<provider>.jsonexists. The peer ran and received the content but returned nothing in the schema shape: unparseable output, or empty findings the script dropped. This is different from not-run: note "cross-model pass: peer ran, no usable output" in human-facing markdown Coverage. Never fail the review. When the diagnostic classification below proves this was a no-review outcome, run the did-not-run fallback before treating the lens as covered. - **Started but not
done** — the final status read reportsfailed,timeout, ordied-without-result(a job reaped at the shared deadline recordstimeout, with the reap noted in its reason) → still non-blocking, but never silent: name the peer and its terminal state in Coverage (e.g. "cross-model adversarial peer: timeout"). A pass that never started gets no terminal-state line; it is reported as not run. When the diagnostic classification below proves this was a no-review outcome, run the did-not-run fallback rather than leaving the lens uncovered. - Classify the skip reason before deleting. Read
out.logbefore cleanup, including bounded lines prefixedpeer skip evidence:. Judge the full diagnostic; do not grep for a closed phrase list. Attribute an account authentication failure only after provider-capable dispatch is positively established by the launch context or provider response; then report the observed failure and login or credential-refresh remediation. Without that proof, login-shaped peer text describes only the peer's execution context: a sandboxed host produces the same signal as a genuine logout, so never report it as the user's account being logged out or prompt a login command. - Any authentication-shaped failure: the peer did not review, whether the explanation is credential-attributable or execution-context-only. Attribution changes the explanation, not coverage; run the did-not-run fallback.
- Transient provider-capacity or rate-limit failure: the worker exclusively owns the one same-route retry under Step 1's overload condition (the worker alone retries, once, after its adapter reports a provider overload, HTTP 529). Once a provider no-review outcome reaches the host, dispatch in-process
adversarial-reviewer; the host never restarts that peer. Do not run the did-not-run fallback or switch recipients. - Max-turn exhaustion: dispatch in-process
adversarial-reviewer. Do not retry the peer or switch recipients. - The requested model itself was refused: the adapter did not offer it, or the provider rejected it for this account (
peer skip evidence (reply):carries a rejection the provider sent as reply text). Only the model failed; the route is still usable. If the worker ran its own default model because none was configured or named by the user, use the lens's one replacement for a new job on the same route. Give it the closest same-family model, picked frompeer skip evidence (advertised models):when that line is present, and bind it as the same-family replacement paragraph inreferences/cross-model-review.mddescribes. If that job also ends in a no-review outcome, take step 2 of the did-not-run fallback. Coverage says which model was rejected and that the user can setcross_model_modelto one that works. A configured or user-named model is never swapped; run the did-not-run fallback. - Any other diagnostic that shows the peer never reviewed the diff: it hit a session or usage quota, or its CLI failed before reviewing, for example by rejecting an option that its installed version does not support. Do not retry that route; run the did-not-run fallback.
- Anything else, including a diagnostic that cannot show whether the peer reviewed: name the observed failure; do not run the did-not-run fallback.
- Did-not-run fallback (first no-review outcome, including the first one): the started job did not cover the adversarial lens. Do not retry that same route. Then:
- If this is the first replacement attempt, the failed recipient was not an explicit user-stated preference (Step 1 item 1), and another attested-different installed+allowlisted target remains: announce that new recipient and start a new job with a new
CROSS_MODEL_FIXED_ROUTE. Wait for it with the remaining shared deadline and fold its artifact the same way as the first job. That job now covers the lens. Never switch recipients inside the worker. If this replacement also ends in a no-review outcome, do not start a third peer; take step 2. - Otherwise (explicit recipient, or no other eligible peer): dispatch in-process
adversarial-reviewernow. Coverage records the peer as not run, with the observed reason, and names the cover in exactly this form:adversarial lens: in-process fallback (cross-model peer not run: <reason>), matchingpeer.outcome: in-process-fallback. Never writeharness-native fallbackhere: that phrase isce-work's ship-gate signal for a review that ran withoutce-code-reviewat all, and reusing it makes a complete review read as a skipped one. A config or default selection is not an explicit user-stated preference. One replacement only; never silently continue to another recipient.
Trust boundary (maintainers)
The peer reviews the current work tree against git diff <base-ref>. Reviewed code/diff content is sent to an external model provider (OpenAI, Anthropic, xAI, Google, or Cursor, depending on the resolved peer). CROSS_MODEL_PEERS restricts which providers may receive content.
Isolation differs from ce-doc-review by design. Doc-review embeds a self-contained document and denies every permission from an empty workspace. Code-review needs surrounding code context, so peers run in-tree: every route runs through acpx (an Agent Client Protocol client, which needs Node 22.13 or newer with npx) from the repository root, and acpx passes it no MCP servers. acpx approves the peer's read and search requests and denies every other permission request, and each adapter runs in its read-only mode where it has one. That removes a tool only where the agent asks permission before using it, so the routes differ in what the peer can still do:
| Route | Reads outside the repository, including the staged large diff | Tools still usable without permission |
|---|---|---|
claude | Allowed (a read permission, which acpx approves) | Reads and searches; read-only shell commands such as git diff. Web tools are denied |
codex | Allowed | Reads anywhere and shell commands, including writes: its adapter does not enforce read-only mode |
grok-cursor, cursor, composer | Allowed | Reads and searches anywhere |
grok-cli | Small diff: refused (it reads through acpx, which rejects paths outside the repository). Large diff: allowed, through its own read tool | Reads inside the repository; web fetch and web search, which its ACP server cannot disable. On a large diff its own tools also write without asking |
opencode | Allowed (its config allows external-directory reads) | Reads anywhere; edits, web, and subagents are denied in its config |
antigravity | Allowed (a read permission, which acpx approves) | Reads and searches. Shell commands and edits ask permission and are denied. It loads skills only from its private per-run home and the repository's .agents/skills and .gemini/skills |
On a diff too large to inline, every route gets the staged diff's private path instead of the diff. grok-cli then runs with its own file tools, because acpx's file access refuses that path. Claude starts in safe mode and OpenCode with OPENCODE_DISABLE_PROJECT_CONFIG=1, so the reviewed repository's Claude settings and hooks and its .opencode/{plugin,agent} do not load. The worst case is disclosure of the reviewed content, except on the codex route, and on grok-cli with a large diff, where a peer could also change files. The script's stderr audit log records each send so every transfer is auditable even in mode:agent.