Watch mode

Shipmoor Team
June 27, 2026
6 min read

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 harness subcommand of the single shipmoor binary (bundled since 0.5.0; current version 0.5.1). A standalone shipmoor-harness script is recognized only as a legacy alias.

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 codeMeaning
0OK
1Blocked (a threshold breach in block mode)
2Error (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
FilePurpose
.shipmoor/watch.pidPidfile; the launching command returns only once it’s written, so --stop never races startup.
.shipmoor/watch.logStdout/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 watch in a terminal beside your session. Mode routing still applies, so block mode exits 1 on a threshold breach.
  • A continuous scan-on-change stream: --emit json or --emit file for logs, a dashboard, or the Cursor soft adapter, which reads last-watch.json when a setup declines native hooks.
  • Scripts and scaffolding: --once scans 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

Last updated on June 27, 2026

Was this article helpful?

Your response is saved on this device.