Task #4 PI231 spike per ADR 0014 Amendment 1 § A1.2 Layer 1. Both providers PASS: claude v2.1.152: HOME redirected 100% of state writes — .claude.json (23KB), projects/, sessions/, backups/, .cache/. Real ~/.claude.json untouched. Symlinked credentials worked. codex v0.133.0: CODEX_HOME redirected ALL state — models_cache (200KB), 3 SQLite DBs (~250KB), cache, sessions, memories, skills, plugin clones. Real ~/.codex untouched. Symlinked auth.json worked. Architecture claims validated. Tasks #5-#8 unblocked. Caveats: codex refuses PATH-helper install under /tmp (warning, not blocker). codex v0.133.0 dropped --ask-for-approval; use -c approval_policy=never. Vibe not installed on PI231; spike deferred. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
12 KiB
PI231 Spike — Ephemeral $HOME / $CODEX_HOME Override Verification
Date: 2026-05-29
Operator: project maintainer (via PI231 SSH)
Spike artifact: tlab@172.16.2.231:/tmp/olp-spike-20260529-100243/
ADR context: ADR 0014 Amendment 1 § A1.2 Layer 1 — "Per-spawn ephemeral home directory"
Task ref: OLP task list #4 ("PI231 spike — verify Claude / Codex HOME / CODEX_HOME override behavior")
TL;DR
Both providers PASS. Setting HOME (claude) and CODEX_HOME (codex) before spawn redirects 100% of CLI state writes into the ephemeral location. Real ~/.claude/, ~/.claude.json, and ~/.codex/ were unmodified by the spike. Credentials accessed via symlink work end-to-end (model returned "PONG" for both providers). Solution 1 is implementable today; Tasks #5-#8 unblocked.
One non-blocking caveat for codex (PATH-helper installation refused under /tmp paths — § Caveats).
Vibe (mistral) not on PI231; pinned to a follow-up spike when the CLI is installed.
1. Environment
| Component | Value |
|---|---|
| Host | tlab@172.16.2.231 (RPi4-P8-231, Debian Bookworm arm64) |
Real ~ |
/home/tlab |
claude |
/home/tlab/.npm-global/bin/claude — v2.1.152 |
codex |
/home/tlab/.npm-global/bin/codex — v0.133.0 |
vibe |
not installed |
| Prod OLP | running (port 4567 with OLP_SANDBOX_DISABLED=1) — spike does not interfere |
Pre-state mtimes (from spike pre-mtimes.txt):
1779999955 /home/tlab/.claude.json
1779999956 /home/tlab/.claude/.credentials.json
1779759544 /home/tlab/.codex/auth.json
Marker file timestamps (pre-spike) used to detect any post-spike write to real home.
2. Methodology
Both providers tested per the same skeleton:
SPIKE_ROOT=/tmp/olp-spike-<timestamp>
mkdir -p $SPIKE_ROOT/<provider>-home/.<provider>
ln -s ~/.<provider>/<credential-file> $SPIKE_ROOT/<provider>-home/.<provider>/<credential-file>
<ENV_OVERRIDE>=<path> timeout 90 <provider> <invocation> "say PONG and nothing else"
find $SPIKE_ROOT/<provider>-home -printf "%y %M %s %p\n" # what landed in fake home
find ~/.<provider> ~/.<provider>.json -newer <marker> # did real home get modified
The find -newer <marker> test is the load-bearing assertion: if it returns empty, the redirect held perfectly. If it returns any path, the CLI silently fell back to the real $HOME-derived path despite the env override.
3. Phase B — claude CLI (anthropic)
3.1 Invocation
HOME=$SPIKE_ROOT/claude-home timeout 60 claude \
--print "say PONG and nothing else" \
--no-session-persistence \
--model claude-sonnet-4-6
Credentials linked: $SPIKE_ROOT/claude-home/.claude/.credentials.json → /home/tlab/.claude/.credentials.json
3.2 Result
PONG
Exit 0. Model returned through Anthropic API via OAuth token from the symlinked real credentials. End-to-end success.
3.3 Fake home post-state (decisive evidence)
Files written under $SPIKE_ROOT/claude-home/:
.claude/.credentials.json (symlink — unchanged)
.claude/projects/-home-tlab/<uuid>.jsonl (135 bytes — project transcript)
.claude/projects/-home-tlab/memory/ (created)
.claude/sessions/ (drwx------ private)
.claude/backups/.claude.json.backup.1780012965052 (50 bytes — pre-write backup)
.claude.json (23,182 bytes — fresh)
.cache/claude-cli-nodejs/-home-tlab/mcp-logs-claude-ai-Gmail/<ts>.jsonl
.cache/claude-cli-nodejs/-home-tlab/mcp-logs-claude-ai-Google-Calendar/<ts>.jsonl
.cache/claude-cli-nodejs/-home-tlab/mcp-logs-claude-ai-Google-Drive/<ts>.jsonl
.claude.json (23 KB) was written to the ephemeral location. This is the file whose non-atomic write upstream (anthropics/claude-code#29250) drove ADR 0014 Amendment 1 § A1.1.2. The Solution 1 architecture removes the maintenance-treadmill concern by letting this file land in tmpfs — confirmed working.
projects/-home-tlab/ — claude encodes the spawn CWD (/home/tlab) by replacing / with -. Not relevant to isolation; would also be the path if claude ran with the real $HOME.
3.4 Real home post-state
$ find ~/.claude.json ~/.claude -newer $SPIKE_ROOT/marker
# (empty)
$ stat -c "%Y %n" ~/.claude.json ~/.claude/.credentials.json
1779999955 /home/tlab/.claude.json
1779999956 /home/tlab/.claude/.credentials.json
Both mtimes identical to pre-state. Real ~/.claude.json was not touched by the spike.
3.5 Verdict
✅ PASS. claude v2.1.152 honours HOME env override completely. All state writes redirect to the ephemeral location. Symlinked credentials work for auth. The Layer 1 + Layer 2 architecture per ADR 0014 Amendment 1 § A1.2 is implementable for anthropic without further work.
4. Phase B — codex CLI (openai)
4.1 Invocation (final, working)
The first attempt used --ask-for-approval never per docs found in pre-spike research — that flag has been removed in codex v0.133.0. Help output shows it must be passed as a config override: -c approval_policy="never". Retry:
echo "say PONG and nothing else" | \
HOME=$SPIKE_ROOT/codex-home \
CODEX_HOME=$SPIKE_ROOT/codex-home/.codex \
timeout 90 codex exec \
--skip-git-repo-check \
-c approval_policy=\"never\" \
--sandbox read-only \
"say PONG and nothing else"
Credentials linked: $SPIKE_ROOT/codex-home/.codex/auth.json → /home/tlab/.codex/auth.json
4.2 Result
WARNING: proceeding, even though we could not update PATH: Refusing to create
helper binaries under temporary dir "/tmp"
(codex_home: AbsolutePathBuf("/tmp/olp-spike-20260529-100243/codex-home/.codex"))
Reading additional input from stdin...
OpenAI Codex v0.133.0
--------
workdir: /home/tlab
model: gpt-5.5
provider: openai
approval: never
sandbox: read-only
reasoning effort: none
reasoning summaries: none
session id: 019e710b-dc28-79f0-854a-06be116b4830
--------
user
say PONG and nothing else
...
codex
PONG
tokens used
10,826
Exit 0. Model invoked, returned "PONG", session id assigned.
The WARNING is significant — see § 5 Caveats. Key fact: the warning's path embed codex_home: AbsolutePathBuf("/tmp/olp-spike-…") proves CODEX_HOME was parsed and honoured. The warning is a narrow refusal (PATH helper binary install), not a refusal of CODEX_HOME itself.
4.3 Fake home post-state (decisive evidence)
.codex/auth.json (symlink — unchanged)
.codex/models_cache.json (200,842 bytes)
.codex/installation_id (36 bytes)
.codex/cache/codex_apps_tools/<hash>.json (92,600 bytes)
.codex/goals_1.sqlite (24,576 bytes)
.codex/logs_2.sqlite (49,152 bytes)
.codex/state_5.sqlite (180,224 bytes)
.codex/shell_snapshots/ (created)
.codex/memories/ (created)
.codex/skills/ (created)
.codex/sessions/2026/05/29/ (date-partitioned)
.codex/.tmp/plugins-clone-<rand>/.git/... (cloned plugins repo)
State scale: ~500 KB across 3 SQLite DBs + model cache + plugin checkout. Far more than claude writes. All of it landed in the ephemeral location.
4.4 Real home post-state
$ find ~/.codex -newer $SPIKE_ROOT/codex-marker3
# (empty)
$ stat -c "%Y %n" ~/.codex/auth.json
1779759544 /home/tlab/.codex/auth.json
Mtime unchanged. Real ~/.codex was not touched by the spike.
4.5 Verdict
✅ PASS. codex v0.133.0 honours CODEX_HOME env override for ALL state files. Symlinked auth artifact works for API authentication. The codex inner sandbox (read-only by default per ADR 0002 Amendment 9 § Per-provider codex declaration) initialized and ran without error.
5. Caveats
5.1 codex PATH helper warning
Codex's startup includes a step that tries to install helper binaries into PATH (presumably under $CODEX_HOME/bin/ or similar). When $CODEX_HOME is under /tmp/, codex refuses this step for security reasons (anti-prefix-attack on PATH):
WARNING: proceeding, even though we could not update PATH:
Refusing to create helper binaries under temporary dir "/tmp"
Impact for OLP: none of the load-bearing functionality is affected. The model invocation completed, auth worked, all session state landed in $CODEX_HOME. The skipped step is for shell-completion-style helpers that the spawn-binary architecture does not need.
If we ever do need those helpers: ephemeral root would need to move out of /tmp/. Candidates: /var/lib/olp-spawn/<keyId>/<reqId>/ (operator-managed) or ~/.olp/spawn/<keyId>/<reqId>/ (within OLP's own data root). Decision deferred — not required for Phase 7 implementation.
5.2 claude project-path encoding (-home-tlab)
claude encodes the spawn cwd into project paths by replacing / with -. The encoded value reflects the real cwd at spawn time (/home/tlab → -home-tlab), not the ephemeral $HOME. This is expected: cwd is a separate input from $HOME.
Impact for OLP: none. The encoding is internal to claude's project tracking. OLP spawn pipeline already runs each request from a per-spawn cwd if it wants to isolate cwd separately; that is orthogonal to Layer 1's $HOME redirect.
5.3 codex v0.133.0 flag set drift
The pre-spike research cited --ask-for-approval never as the non-interactive approval flag (sourced from OpenAI docs pages indexed before v0.133.0 changed the flag layout). v0.133.0 instead requires -c approval_policy="never" via the generic config-override flag. ADR 0002 Amendment 9 § codex toolHardeningArgs declaration uses --sandbox read-only which is still a valid top-level flag; no amendment update required. Implementation note (Task #7): codex.mjs toolHardeningArgs should not inject --ask-for-approval — use -c approval_policy="never" if the policy needs to be locked at spawn time.
5.4 Mistral vibe CLI not present on PI231
which vibe returned empty. Vibe is not currently part of the PI231 test deployment per the topology memory (~/.cc-rules/memory/projects/olp/topology_pi231_server_2026_05_27.md). The ADR 0002 Amendment 9 mistral declaration uses VIBE_HOME per the Mistral docs page (3 occurrences verified at amendment time). The observed-behavior verification is a follow-up spike triggered when vibe is installed.
6. Implications for ADR 0014 Amendment 1
| Architecture claim | Spike result |
|---|---|
Layer 1 (ephemeral $HOME / $CODEX_HOME) is implementable |
✅ Confirmed for anthropic + codex |
~/.claude.json upstream non-atomic-write concern is solved by redirect |
✅ Confirmed — write lands in tmpfs .claude.json, real one untouched |
| Layer 2 (symlinked credentials) preserves auth | ✅ Confirmed — both providers authenticated via symlink |
| codex inner sandbox composes with Layer 1 (no nested-bwrap conflict) | ✅ Confirmed — codex --sandbox read-only initialized and ran |
| Solution 1 obsoletes outer-bwrap maintenance treadmill | ✅ Confirmed — no --ro-bind mount patches required |
No architectural changes required. ADR 0014 Amendment 1 is validated by primary-source observation on the target deployment.
7. Unblocked / next
Tasks unblocked by this spike's PASS verdict:
- Task #5 — refactor
lib/sandbox/manager.mjstoprepareIsolatedEnvironment()per Layer 1 + Layer 2 - Task #6 — add
ISOLATIONblock tolib/providers/anthropic.mjs - Task #7 — add
ISOLATIONblock tolib/providers/codex.mjs(use-c approval_policy="never"per § 5.3, not--ask-for-approval) - Task #8 — wire
prepareIsolatedEnvironmentintoserver.mjsspawn site
Follow-ups not blocking:
- Vibe spike when CLI is installed (verify documented
VIBE_HOMEbehavior matches observed) - codex PATH-helper out-of-
/tmpconsideration if the helpers ever become required
8. Artifact retention
The spike root /tmp/olp-spike-20260529-100243/ on PI231 is automatically cleaned by tmpfs lifetime / reboot. No commit of binary artifacts. Evidence above is the canonical record.
Authored by project maintainer 2026-05-29; commands executed on PI231 with maintainer's SSH session.