From ffe81f7a4558d65bfc5111b9057228210ff081f4 Mon Sep 17 00:00:00 2001 From: dtzp555-max Date: Fri, 29 May 2026 10:14:25 +1000 Subject: [PATCH] =?UTF-8?q?docs(spike):=20PI231=20verify=20HOME/CODEX=5FHO?= =?UTF-8?q?ME=20ephemeral=20redirect=20=E2=80=94=20Solution=201=20PASS=20(?= =?UTF-8?q?#67)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Co-authored-by: Claude Opus 4.7 --- docs/spikes/2026-05-29-ephemeral-home.md | 274 +++++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 docs/spikes/2026-05-29-ephemeral-home.md diff --git a/docs/spikes/2026-05-29-ephemeral-home.md b/docs/spikes/2026-05-29-ephemeral-home.md new file mode 100644 index 0000000..6059f3f --- /dev/null +++ b/docs/spikes/2026-05-29-ephemeral-home.md @@ -0,0 +1,274 @@ +# 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: + +```bash +SPIKE_ROOT=/tmp/olp-spike- +mkdir -p $SPIKE_ROOT/-home/. +ln -s ~/./ $SPIKE_ROOT/-home/./ + += timeout 90 "say PONG and nothing else" + +find $SPIKE_ROOT/-home -printf "%y %M %s %p\n" # what landed in fake home +find ~/. ~/..json -newer # did real home get modified +``` + +The `find -newer ` 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 + +```bash +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/.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/.jsonl +.cache/claude-cli-nodejs/-home-tlab/mcp-logs-claude-ai-Google-Calendar/.jsonl +.cache/claude-cli-nodejs/-home-tlab/mcp-logs-claude-ai-Google-Drive/.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 + +```bash +$ 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: + +```bash +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/.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-/.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 + +```bash +$ 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///` (operator-managed) or `~/.olp/spawn///` (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.mjs` to `prepareIsolatedEnvironment()` per Layer 1 + Layer 2 +- Task #6 — add `ISOLATION` block to `lib/providers/anthropic.mjs` +- Task #7 — add `ISOLATION` block to `lib/providers/codex.mjs` (use `-c approval_policy="never"` per § 5.3, not `--ask-for-approval`) +- Task #8 — wire `prepareIsolatedEnvironment` into `server.mjs` spawn site + +Follow-ups not blocking: +- Vibe spike when CLI is installed (verify documented `VIBE_HOME` behavior matches observed) +- codex PATH-helper out-of-`/tmp` consideration 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.