In release — preview. This surface is fully built and in the release process; it is not yet generally available. Treat the commands and behavior here as preview until GA.
The point of the harness is one closed loop: the agent edits, the edit is scanned, a finding goes straight back to the agent, and the agent fixes its own mistake before you ever see it. This page walks that loop on Claude Code — the harness’s primary agent — using output from a real session against shipmoor 0.4.0.
The wiring
shipmoor-harness install claude injects two hooks into .claude/settings.json’s top-level hooks key, beside any hooks you already have:
PostToolUse(matchingEdit|MultiEdit|Write|NotebookEdit) — scans after every edit.Stop— a final pass when the agent finishes.
Claude Code invokes the handler with the tool payload on stdin:
shipmoor-harness hook claude-code post-tool-use
# stdin: {"hook_event_name":"PostToolUse","tool_name":"Edit","tool_input":{"file_path":"pay.py"}}
The handler finds the changed file from the payload (falling back to scan --changed), scans it with the real CLI, and answers in the project’s configured mode.
Turn 1 — block
The agent edits pay.py and hallucinates a dependency: import receipt_engine. The hook scans the edit, finds the phantom import, and stops the tool — exit code 2, findings on stderr, which Claude Code feeds back to the model:
[shipmoor-harness] Shipmoor found 1 issue (1 high).
[high] pay.py:6 python.phantom_import
Local module 'receipt_engine' is referenced but no file matches under PYTHONPATH.
→ 'receipt_engine' looks local but does not resolve from project module paths.
Add the file, fix PYTHONPATH, or remove the import.
Turn 2 — self-correction
The agent, seeing exactly which line and why, removes the import. The hook re-runs on the corrected edit and clears: exit 0, no output. The loop closed without a human in it.
A clean change is always silent — the harness never adds noise to a session that doesn’t need it. And if the CLI is missing or errors, the hook degrades to a no-op rather than breaking your agent.
Feedback mode — advise, don’t stop
Under mode feedback, the same finding never blocks. The handler exits 0 and returns the finding in Claude Code’s documented hook-response shape, landing in the model’s context as additional information:
{"hookSpecificOutput": {"hookEventName": "PostToolUse",
"additionalContext": "Shipmoor found 1 issue (1 high).\n\n[high] pay.py:6 python.phantom_import\n Local module 'receipt_engine' is referenced but no file matches under PYTHONPATH.\n → 'receipt_engine' looks local but does not resolve from project module paths. Add the file, fix PYTHONPATH, or remove the import."}}
The feedback text is deterministic — severity-ordered, path:line, rule ID, recommendation — formatted from the parsed JSON report, never from scraped human output.
The loop is bounded
harness.max_feedback_cycles (default 3) caps how many times findings are routed back within one loop. When the cap is reached, whatever remains is surfaced instead of re-prompted — the agent can never ping-pong forever against a finding it can’t fix.
Try it yourself
Dry-run the handler with a synthetic payload before relying on it:
printf '{"tool_name":"Edit","tool_input":{"file_path":"path/to/edited.py"}}' \
| shipmoor-harness hook claude-code post-tool-use
Next
- Install & modes — choosing block vs feedback per project.
- Agent support — the same loop on Aider, Codex, and Cursor.
- Findings & rules — what the scan hands back.