Troubleshooting

Diagnostic codes from guarded runs and what to do about them.

First steps

Every run writes task_outcome.json with a diagnostic_code and a next_command. Start there:

bash
cco run ... --json        # prints the outcome, including diagnostic_code
cco trace <run_id>        # inspect the recorded run

Exit codes: 0 kept, 1 rolled back or failed, 2 blocked before execution.

Dirty worktree (dirty_worktree)

Symptom: cco run exits 2 with dirty_worktree before the agent starts.

Rollback uses git reset --hard and git clean -fd, which is only safe from a clean tree, so the preflight refuses anything else - including untracked files.

bash
git status --short
git stash -u        # or commit / discard the changes, then rerun

Also check not_a_git_repo: run from the root of a git work tree or pass --repo.

verify_command_rejected

Symptom: The run is blocked with verify_command_rejected or verify_command_requires_shell.

Verify commands are shell-free: each is split into argv and must be in the command allowlist. Shell syntax (| && ; > $() is refused with verify_command_requires_shell. Split compound checks into separate plain entries, or add the command to command_allowlist in the cco config. Routing does not retry this error, because it would fail identically at every tier.

unexpected_mutation rollbacks

Symptom: The run was rolled back with unexpected_mutation and rejected.patch exists.

The scope check found a change outside allowed_files, inside forbidden_paths, or in a protected path. Read scope_violations in task_outcome.json for the exact paths and kinds. Either widen allowed_files in the contract or narrow the task.

Verify must be side-effect-free. Test runners that write caches, build output or snapshot files outside the allowed set trigger the same rollback after a passing test. Point those outputs at ignored benign cache directories or add the files to allowed_files.

executor_sandbox_disabled (degraded)

Symptom: The run was kept but its state is degraded with executor_sandbox_disabled, so a contract is not accepted.

The executor supports a sandbox but it was not requested. Rerun with --executor-sandbox to produce a ready outcome. If the cco config sets runner.require_sandbox: true, the run is instead blocked with executor_sandbox_required. The opencode executor has no sandbox the runner can request; --executor-sandbox is ignored there.

bash
cco run "<task>" --contract-file <path> --executor claude --executor-sandbox

Blocked protected paths

Symptom: A run is rolled back because it touched .github/workflows/**, AGENTS.md, .context/** or .git/**.

These paths are protected even when a contract lists them as allowed; the violation shows as kind protected_path in scope_violations. Make the change by hand outside the runner, then rerun the rest of the task. Related diagnostics: executor_moved_head (the agent committed or switched branches; HEAD is reset) and git_metadata_tampered (config or hooks are restored from the baseline).

Routing escalation exhaustion

Symptom: A --task-class run ends failed after several attempts, or is blocked with routing_not_configured / unknown_task_class.

The routing.stop_reason in execution_envelope.json says why escalation ended: max_escalations (raise runner.routing.max_escalations), last_tier (there is no stronger tier), budget (cumulative cost reached max_budget_usd_per_task), non_retryable (for example a timeout or rejected verify command) or blocked_after_escalation (the next tier's executor was unavailable). Each attempt has its own run directory listed in routing.attempts. routing_not_configured and unknown_task_class mean the config lacks runner.routing or the class name.

Executor problems

Symptom: executor_unavailable, executor_cli_missing, executor_flag_unsupported, executor_timeout or noop_not_allowed.

Install the executor CLI and put it on PATH; cco run --help lists executors. executor_flag_unsupported means your installed CLI lacks a flag the adapter uses. For executor_timeout raise --executor-timeout-seconds (default 1800). noop_not_allowed means the agent changed nothing; set allow_noop in the contract if that is acceptable. If rollback_failed appears, follow the manual recovery commands in next_command.

Observatory shows aborted or stale runs

Symptom: The observatory lists runs as aborted or stale instead of finished.

A run without task_outcome.json is aborted if its last phase is already decision or rollback (the runner was killed after deciding), and stale if no event arrived for 30 minutes. Neither counts toward kept or rollback rates. Check that the process is gone, then inspect phase_events.jsonl and the executor logs in that run directory. If the whole page shows a "connection lost" banner, the server is not running: restart cco observatory, and confirm the port with --port.