Watch mode is the harness’s background, agent-agnostic option: for an editor with no native hook support, or whenever you want a continuous scan-on-change event stream beside your work, the harness can watch the working tree and run one scan per burst of edits. It is now the secondary mechanism. If your agent has native hooks (Claude Code and Cursor do), prefer those plus the one-shot shipmoor harness scan as the per-edit primitive. The detached watch daemon is not the recommended way to wire up a hook; reach for watch when there is no hook surface at all, or when you specifically want a long-running scan-on-change stream.
The harness is the
harnesssubcommand of the singleshipmoorbinary (bundled since 0.5.0; current version 0.5.1). A standaloneshipmoor-harnessscript is recognized only as a legacy alias.
One-shot: the recommended hook/CI primitive
Before watch mode, know the primitive you usually want instead. shipmoor harness scan (alias check) runs one scan --changed pass over the current change-set, writes .shipmoor/last-watch.json, and exits with the mode’s code. No poll baseline, no debounce, no waiting for a future edit. That is exactly what a hook or a CI step needs.
shipmoor harness scan # one scan, write the report, exit with the mode code
shipmoor harness check # the same one-shot (alias)
shipmoor harness scan --emit json # also print one privacy-safe event line
shipmoor harness scan --emit none # report only, no event line (the default)
| Exit code | Meaning |
|---|---|
0 | OK |
1 | Blocked (a threshold breach in block mode) |
2 | Error (gate locked or the scan failed) |
--emit json prints a single event line, the same privacy-safe shape the watch stream uses (see below). These are the harness wrapper’s own codes, distinct from the CLI scanner’s 0/1/2/3. See Output & exit codes for the scanner side.
Run the watcher
shipmoor harness watch is a debounced scan-on-change loop: it polls the working tree, coalesces a burst of edits, and runs one scan per burst.
shipmoor harness watch --emit json # one event per scan, on stdout
shipmoor harness watch --emit file # events to .shipmoor/watch.events.jsonl
shipmoor harness watch --once # scan what's changed now, then exit
shipmoor harness watch --emit json --debounce-ms 500 # tune the burst window
The debounce window defaults to 750 ms and is configurable via harness.watch.debounce_ms in .shipmoor.yaml (or --debounce-ms for a one-off). A burst of rapid edits collapses into a single scan, so an agent writing ten files doesn’t trigger ten scans. SIGINT/SIGTERM shut the loop down cleanly.
The watcher always excludes .git, .shipmoor, node_modules, __pycache__, and .venv, and it honors your .gitignore entries on top of those. (Excluding .shipmoor/ is what keeps writing last-watch.json from retriggering the watcher.) Inside a git repo the candidate set is git’s own tracked-plus-untracked-not-ignored files, so a large vendored tree is never walked.
--once is the same one-shot as shipmoor harness scan; it scans the current change-set and exits immediately, so it never hangs on an empty or fully-ignored tree. --once, --daemon, and --stop are mutually exclusive.
The event stream (privacy-safe)
Each scan emits one JSON line. This is a real event from a dogfood run, a single phantom-import finding in pay.py:
{"event":"scan","paths":["pay.py"],"findings_summary":{"counts_by_severity":{"critical":0,"high":1,"medium":0,"low":0,"info":0},"rule_ids":["python.phantom_import"],"total":1},"exit_code":0}
That is the whole event, by design. It carries only paths, counts_by_severity, rule_ids, and exit_code, never source code, never finding prose. The stream is safe to pipe into logs or a dashboard without re-running the privacy review your repository’s source would need. --emit json writes these lines to stdout; --emit file appends them to .shipmoor/watch.events.jsonl (rotated past 1 MiB).
The full findings, for the tool that should see them, land in .shipmoor/last-watch.json: a versioned report (schema: shipmoor.harness.report.v1) carrying mode, paths, counts_by_severity, exit_code, the formatted feedback, and findings. Consumers like the Cursor adapter read that file locally. Its findings[] are the same finding objects shipmoor scan --json emits, passed through unchanged: the full scan.v1 finding (rule_id, severity, path, start_line, message, root_cause, recommendation, fingerprint, id, language, change_status, …). scan.v1 is the single source of truth for that shape, so a custom adapter parses one canonical finding, validated against the scan.v1 contract, whether it reads the scan output or this report.
Background daemon
To run the watcher detached from your terminal, use --daemon. It double-forks off the controlling terminal, ignores SIGHUP so it survives a lost TTY, and is idempotent; a second --daemon no-ops while one is live.
shipmoor harness watch --daemon # run detached in the background
shipmoor harness watch --stop # stop the running daemon
| File | Purpose |
|---|---|
.shipmoor/watch.pid | Pidfile; the launching command returns only once it’s written, so --stop never races startup. |
.shipmoor/watch.log | Stdout/stderr logfile, capped at 1 MiB (rotated to watch.log.1). |
A daemon must run in observe or feedback mode. block is a foreground gate that fails a build on a breach, so it is refused for --daemon (a detached watcher has no build to fail). For a per-edit gate, use a native hook plus the one-shot instead of a daemon.
When to reach for watch mode
- An editor or agent with no hook surface: run
watchin a terminal beside your session. Mode routing still applies, soblockmode exits1on a threshold breach. - A continuous scan-on-change stream:
--emit jsonor--emit filefor logs, a dashboard, or the Cursor soft adapter, which readslast-watch.jsonwhen a setup declines native hooks. - Scripts and scaffolding:
--oncescans what’s changed and exits, which composes cleanly into test and CI scripts.
If your agent does support native hooks, that path plus shipmoor harness scan catches the same changes per edit without a long-running process; prefer it.
Next
- Self-correction loop: how findings flow back into the agent.
- Using the harness with Cursor: native hooks vs the soft adapter that consumes the watcher.
- Agent support: how the Cursor adapter integrates (native hooks, with a soft watcher fallback).
- Install & modes: the mode the watcher routes with.
- Output & exit codes: the scanner codes behind the harness wrapper.
- Privacy & telemetry: the broader local-first guarantees.