Goal
A throwaway environment that exercises claudectl's real discovery → monitor → process → brain pipeline against controllable fixtures, for both automated e2e assertions and interactive "experience" testing — without real Claude Code agents or spend.
Key insight
Everything claudectl treats as reality derives from $HOME (dirs_home()), so the isolation lever is a temp $HOME (redirects both .claude and .claudectl). Surfaces to fake:
| Surface |
Where |
Synthesize |
| Session registry |
$HOME/.claude/sessions/*.json |
write JSON |
| Transcript |
$HOME/.claude/projects/<enc-cwd>/<id>.jsonl |
append JSONL on a timeline |
| Liveness/CPU |
real ps — a PID only counts if its command contains "claude" (process.rs:54) |
spawn a placeholder process argv'd as claude |
| Brain |
HTTP to brain.endpoint |
mock LLM server returning canned {action,confidence} |
| coord/bus |
$HOME/.claudectl/* |
fresh temp DBs; drive via supervisor run |
The non-obvious constraint: in-memory fakes (demo.rs) bypass the real pipeline; faithful e2e needs real PIDs.
Design — two tiers
- Tier A (CI):
SynthEnv (temp $HOME) + SynthSession (placeholder claude proc + session JSON + emit_* methods) + scenario library (healthy/stall/loop/budget/conflict/needs-input) → run headless claudectl --json/--watch and assert on output.
- Tier B (experience): same env, launch the TUI with a scenario driver mutating fixtures over time; capture with
recorder/session_recorder.
Components
SynthEnv (temp HOME + teardown that kills procs)
SynthSession (spawn + JSON + emit_message/emit_tool_use/emit_usage/set_needs_input/stall/finish)
- Scenario library (port
demo.rs timelines to real fixtures)
- Mock brain server (~30-line stub)
- Runner (headless-assert + TUI/record)
Build order
Tier A first (SynthEnv + one scenario + a headless assertion) — it's the reusable foundation. Lives in tests/e2e/ (+ optionally a claudectl simulate subcommand for Tier B).
Related: #373 (demo tour, in-memory) is the lighter-weight cousin.
Goal
A throwaway environment that exercises claudectl's real discovery → monitor → process → brain pipeline against controllable fixtures, for both automated e2e assertions and interactive "experience" testing — without real Claude Code agents or spend.
Key insight
Everything claudectl treats as reality derives from
$HOME(dirs_home()), so the isolation lever is a temp$HOME(redirects both.claudeand.claudectl). Surfaces to fake:$HOME/.claude/sessions/*.json$HOME/.claude/projects/<enc-cwd>/<id>.jsonlps— a PID only counts if its command contains"claude"(process.rs:54)claudebrain.endpoint{action,confidence}$HOME/.claudectl/*supervisor runThe non-obvious constraint: in-memory fakes (
demo.rs) bypass the real pipeline; faithful e2e needs real PIDs.Design — two tiers
SynthEnv(temp$HOME) +SynthSession(placeholderclaudeproc + session JSON +emit_*methods) + scenario library (healthy/stall/loop/budget/conflict/needs-input) → run headlessclaudectl --json/--watchand assert on output.recorder/session_recorder.Components
SynthEnv(temp HOME + teardown that kills procs)SynthSession(spawn + JSON +emit_message/emit_tool_use/emit_usage/set_needs_input/stall/finish)demo.rstimelines to real fixtures)Build order
Tier A first (
SynthEnv+ one scenario + a headless assertion) — it's the reusable foundation. Lives intests/e2e/(+ optionally aclaudectl simulatesubcommand for Tier B).Related: #373 (demo tour, in-memory) is the lighter-weight cousin.