watch · git:20260905.024d74d · 2026-09-05 · sha256 a47a893de41659df

watch git:20260905.024d74dA

Immutable. This exact content is served forever at /api/v1/blob/a47a893de41659df.

---
name: watch
description: "Use when a Nightwatch spec queue is about to run, or is already running, and someone needs to fire it, watch it, and steer it — \"launch nightwatch\", \"watch the run\", \"/nightwatch:watch\", \"pause it\", \"skip that spec\". Runs preflight, launches run.sh, arms the journal and workflow-journal monitors, knows what is safe to change mid-run, and reads the interventions (control file, pause file, kill switch) plus when to hand off to morning.mjs. Do NOT use to write a spec (nightwatch:spec) or to implement the outcome's code yourself — the launcher's `claude -p` units do that, never this session."
---

# Watch a Nightwatch run

The first night failed five ways in the harness and once in the code: a
print-mode timeout that fabricated a result, an env var that silently forced
default permission mode, an untrusted clone that dropped project settings, a
`tee` that polluted the state channel, and a dirty-tree false positive on a
file the acceptance command itself wrote — against one real spec defect (an
Earnings-21 reference file with empty timestamp columns). All five harness
failures are fixed in the engine now; this playbook is for the failures a
fixed engine still can't prevent — a blocked branch, a spec that needs an
edit mid-run, a night that needs to stop early — and for reading the state a
running launcher writes so nothing has to be guessed.

Announce: "Using nightwatch:watch to launch and steer the run."

## 1. Preflight

Before starting `run.sh`, confirm all of:

- The clone is clean (`git status --porcelain` empty) and trusted (`claude
  -p` in it reads project settings — an untrusted workspace silently drops
  them).
- `gh variable get LANDING_STATE` (from inside the clone; the launcher's
  default `STATE_VAR`) prints exactly `run`.
- `node <plugin>/nightwatch/lint-spec.mjs --specs-dir <specs-dir> --check <check>`
  prints `SPEC OK (<n> specs)` for the whole queue — a spec defect caught
  here is free; caught at 2 a.m. it costs a unit.
- 1Password is unlocked if any command in the queue needs a secret.
- `$HOME/.local/state/nightwatch/<name>/launcher.lock` is absent, or holds a
  pid that is no longer alive (`kill -0 <pid>` fails) — a live lock means
  another launcher already owns this clone. `node <plugin>/nightwatch/init.mjs
  --report` re-checks a setup already done — a clean way to confirm nothing
  has drifted (trust, the check command, the switch) before a launch.

## 2. Launch

```
caffeinate -i <plugin>/nightwatch/run.sh <name> [<specs-dir>]
```

`<specs-dir>` defaults to the config's `SPECS`, so a bare `<name>` is enough
once init has run. In a `herdr` pane when `HERDR_ENV` is set, so the run has
its own pane and this session never blocks on it; otherwise background it
with stdin from `/dev/null` (the child reads stdin even given a prompt, and
would otherwise eat this session's own input). `--only <slug>[,<slug>...]`
narrows the queue for a resume or a single-spec relaunch.

State lives at `$HOME/.local/state/nightwatch/<name>` — keyed on the name in
`~/.local/state/nightwatch/<name>/config`, the same name `init.mjs` and
`morning.mjs` take. `<state>/journal.md` is the event log;
`<state>/runs/<stamp>/` holds this launch's per-unit files.

## 3. Arm two monitors

- The journal: `tail -f <state>/journal.md` — one line per event (`start:`,
  a unit's state, `control:`, `paused:`, `end:`).
- The workflow journals: `~/.claude/projects/<sanitised clone
  path>/*/subagents/workflows/wf_*/journal.jsonl` — one event per phase
  result (Reconcile, Implement, Verify, Eval), useful when a unit is quiet
  in the launcher journal but still running.

## 4. What's safe to change mid-run, and when it lands

- **Spec files** are re-read at the start of every unit (the launcher
  snapshots the spec into `<state>/outcomes/<slug>/u<n>.spec.md` right
  before it runs the unit) — edit a spec in place and the next unit reads
  the edit. This is how the first night's `cargo run --` fix landed live.
- **`nightwatch.mjs`** (the Workflow script) is invoked fresh by each unit's
  `claude -p`, so an edit takes effect on the next unit that starts.
- **`run.sh` must never be edited in place while a launcher is running** —
  bash reads the script incrementally as it executes, and a mid-file edit
  can corrupt the running process. Write a replacement to a temp file and
  `mv` it over the original; the change then applies only to the *next*
  launch, not the one in flight.

## 5. Interventions

| Need | Command |
|---|---|
| Fix a blocked branch (typo, small edit) | `git worktree add <dir> <branch>` in a **separate** worktree of the same clone's repo, fix, commit, remove the worktree; never touch the launcher's own checkout while it is running |
| Skip a spec without stopping the night | `echo "skip <slug>" >> <state>/control` — if it's the current spec, it ends after this unit as PARTIAL |
| Run a spec again (e.g. after fixing it) | `echo "requeue <slug>" >> <state>/control` — appends it to the queue even if it already ran; a kept branch is resumed |
| End the night after the current unit | `echo "stop" >> <state>/control` |
| Hold the queue without ending the night | `touch <state>/pause` — the launcher waits at the next unit boundary, polling every 30s, and still honours the deadline and the kill switch while paused; `rm <state>/pause` resumes it |
| Hard stop, right now | `gh variable set LANDING_STATE --body frozen` (from the clone) — the launcher checks this before every spec and unit |

`<state>/control` is append-only: always `>>`, never truncate or rewrite it
— the launcher tracks how many bytes it has consumed and a rewrite loses
that offset.

## 6. Keep a running log

Keep a timeline table and a findings list in
`docs/research/<date>-nightwatch-<name>.md` **in the claude-skills
checkout**, never in the launch clone — a file committed there would ride
whatever outcome branch is active straight into that outcome's PR, and an
uncommitted file fails Reconcile's "clone is dirty" check at the next unit.
Commit the log as it grows, the way the first night's log
([`2026-09-05-nightwatch-first-night.md`](https://github.com/jasonm4130/claude-skills/blob/main/docs/research/2026-09-05-nightwatch-first-night.md))
did.

## 7. At the `end:` line

Run `node <plugin>/nightwatch/morning.mjs <name>` and hand its report to the
user — it resolves the name to its state dir, defaulting `--clone` from the
config, and reads the journal, the outcomes, and the verify logs, and writes
`<state>/pr-body.md` with the exact commands to push the landing branch and
open the PR. This skill launches and steers the run;
`morning.mjs` is what reads it afterward, not `nightshift:morning` (that
skill reads the old task-per-PR loop's journal, a different state shape
entirely).