Files
reliquary/docs/workpad/reports/2026-10-16-08-headless-forks-trunk-notes.md
T

37 lines
4.3 KiB
Markdown

# Trunk notes — headless forks build (`spire:forks`)
Written by the Athanor (`claude-opus-5-5[1m]`) at Phase 0, 2026-10-16. For fresh agents and a successor Athanor. A page, not a design.
## Where things are
- Build plan (authoritative): `docs/workpad/plans/2026-10-16-02-headless-forks-build.md`. Plan: `plans/2026-10-16-01-headless-forks.md`. Design: `designs/2026-10-16-01-headless-forks.md` (runtime amendment at the end: Python under uv). Research: `research/2026-10-16-00-claude-headless-fork-sessions.md`.
- Run log: `docs/workpad/reports/2026-10-16-08-headless-forks-run.md`. Preflight report (Phase 0 output, the Phase 1 defaults): `docs/workpad/reports/2026-10-16-09-headless-forks-preflight.md`.
- Checkout: worktree `G:\dev\projects\reliquary\.claude\worktrees\headless-forks`, branch `headless-forks`, cut from `d373689`.
- Phase 0 probes and raw streams (throwaway, not in git): `C:\Users\g4b\AppData\Local\Temp\claude\G--dev-projects-reliquary\42c87f82-5542-46aa-8d01-4166e3f0bcfe\scratchpad\headless-forks-p0\` — `probes/` (code Phase 1 may reuse: `p0lib.py` scrub, command line, stream parser, Windows pid+ctime liveness, job-object query; `spawn3.py` CIM spawn), `raw/` (streams, summaries), `clone/` (scratch clone, untrusted workspace).
- New code goes to `plugins/spire/skills/forks/` only (`forks.py`, `preamble.md`, `templates/`, `tests/test_forks.py`). Nothing else in `plugins/` changes before Phase 3.
## Host facts that shape the helper (measured, see the preflight report)
- `uv` 0.5.13 at `~/.local/bin/uv`; `uv run <abs>/script.py` picks CPython 3.13.1 from a cached archive venv. `sys.executable` is the venv `Scripts\python.exe`; `sys._base_executable` is the real interpreter. A `pyproject.toml` or a `.python-version = 3.9` in the cwd is ignored. uv writes one line to stderr, nothing to stdout. Inside `uv run` the env gains `VIRTUAL_ENV` and a PATH head entry `<venv>\Scripts`; no `UV_*` vars.
- No job objects anywhere (Bash tool, uv, supervisors: `IsProcessInJob` false). The Bash tool's stop kills by **process tree** (parent links). A supervisor whose launcher is still alive dies with the Bash call; one spawned through CIM (`Win32_Process.Create`) is parented to `WmiPrvSE.exe` and survives.
- **No visible windows.** An unhidden CIM spawn opens a Windows Terminal window on the user's desktop (user report). Spawn the supervisor with `Win32_ProcessStartup.ShowWindow=0` and every child of it with `CREATE_NO_WINDOW` (`0x08000000`); never `DETACHED_PROCESS`/`CREATE_NEW_CONSOLE`. Tests check it.
- CIM-spawned processes get the **registry default user env**, not the caller's (no `CLAUDE_CODE_EXECPATH`, no Git Bash PATH). The env for `claude` must be shipped explicitly (file next to the job) and the binary path must be absolute.
- A Bash call that exceeds its `timeout` is **moved to the background**, not killed; its completion notification reached this subagent. Keep the `timeout: 600000` rule anyway.
- Two binaries: PATH `claude` 2.1.283, `$CLAUDE_CODE_EXECPATH` 2.1.299 (Desktop). Pin the latter.
- Host `CLAUDE_EFFORT=high` today (the research saw `medium`); scrubbed anyway.
- Only `G:/dev/projects/reliquary` is trusted in `~/.claude.json`; the worktree inherits trust (no warning); the scratch clone does not (project `permissions.allow` ignored, stderr says so).
## Invariants Phase 1 must keep
- stdout = exactly one JSON object; diagnostics and uv chatter on stderr.
- Usage: fork `result.usage` equals the `modelUsage` delta against the base, field for field; cost delta = `total_cost_usd` minus the base's cumulative total. Stream assistant events carry interim `output_tokens`; never sum them.
- Cache verdict from the **first** assistant event; base context = input + cache creation + cache read of the base's last assistant message (stream and transcript agree).
- Every git call `git --no-optional-locks`.
## Known traps
- `git clone --local` from `G:` to `C:` cannot hardlink; use `--no-hardlinks`.
- Opus 5.5 may refuse a bare "run this blocking command" brief as pointless; say why it is wanted.
- A fork whose Bash tool backgrounds a command leaves that command running after `claude` exits (seen: `sleep 240`, orphaned). `kill` by tree cannot reach it once `claude` is gone.
- Unlisted models are not refused: Claude Code substitutes a permitted model and emits a `system/informational` warning. Detect it; do not trust `init.model` alone.