simulation: isolate claude -p reflections from user-level hooks

In this commit, we point CLAUDE_CODE_CONFIG at a sterile home
directory for every headless reflection call, mirroring the
CODEX_HOME isolation we built for the codex backend. The
--system-prompt replacement already keeps CLAUDE.md and memories out
of the session, but user-level hooks still fire in -p mode and their
feedback text reaches the model: iteration 19 of the first Opus run
came back reasoning about the user's mail-watcher Stop hook instead
of emitting a router, wasting the iteration. With CLAUDE_CONFIG_DIR
pointed at ~/codez/claude-harness-home, no user settings load at all;
auth is unaffected because the OAuth token travels via the
environment.
This commit is contained in:
Olaoluwa Osuntokun 2026-07-25 13:01:36 -07:00
parent bd72d2236b
commit f27bd470ab

View file

@ -13,6 +13,12 @@ tooling chatter instead of candidates. Two defenses here:
model to its role as a text-generation function.
- The subprocess runs from a neutral temporary directory, so project-level
CLAUDE.md discovery has nothing to find even in modes that perform it.
- CLAUDE_CONFIG_DIR points at a sterile home (~/codez/claude-harness-home),
so user-level settings and, critically, user-level HOOKS never load.
Hooks fire even in headless -p mode and their feedback text reaches the
model: one reflection in the first Opus run came back discussing the
user's mail-watcher Stop hook instead of emitting a router. Auth is
unaffected because the OAuth token travels via the environment.
When ANTHROPIC_API_KEY is present we additionally pass `--bare`, which
skips hooks, auto-memory, and CLAUDE.md discovery entirely (bare mode only
@ -82,6 +88,12 @@ class ClaudeLM:
TOKEN_FILE.read_text().strip()
)
# A sterile config home keeps user-level settings and hooks out
# of the session (see module docstring); created on first use.
config_home = pathlib.Path.home() / "codez" / "claude-harness-home"
config_home.mkdir(parents=True, exist_ok=True)
env["CLAUDE_CONFIG_DIR"] = str(config_home)
# The claude binary is a wrapper that spawns the real CLI as a
# grandchild sharing our stdout pipe. subprocess.run's timeout
# kills only the direct child and then blocks forever reading a