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

4.3 KiB

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.