---
status: verifying
trigger: "echo-commune process spawned but never terminated per doyle.log"
created: 2026-04-19T19:19:00-07:00
updated: 2026-04-19T19:40:00-07:00
---

## Current Focus

hypothesis: CONFIRMED and FIXED. Commit f9fdea0 set `cmd.current_dir(owlery::psyche_dir())` so haiku could Read {self_id}.md as a relative path. This broke `claude -p --resume <uuid>` because Claude CLI resolves resumable sessions via `~/.claude/projects/<cwd-encoded-path>/<uuid>.jsonl`. The Self session is registered under the project CWD encoding (e.g. `C--Users-decid-Documents-projects-claude-skill-owl`), not under the psyche_dir encoding (`C--Users-decid-AppData-Local-spt-psyches-tracked`). Direct reproduction: running the exact spawn command from psyches/tracked/ produces "No conversation found with session ID: …" within a second. Claude exits non-zero, stderr is routed to Stdio::null(), stdout is empty → `wait_with_output()` returns empty → `if stdout.is_empty() { return; }` silently bails → no `[COMMUNE]` marker → no `deliver_body_anonymous` → Psyche inbox stays empty → wrapper waits forever for the next pulse. That matches the doyle.log observation: spawn logged, no further action.
test: Ran `claude -p --resume d62938b3-… --model haiku --effort low --disable-slash-commands --allowed-tools "Read Grep Glob Bash(git log:*) Bash(git diff:*) Bash(git status)"` from `C:/Users/decid/AppData/Local/spt/psyches/tracked` → "No conversation found". Session UUID IS registered under `~/.claude/projects/C--Users-decid-Documents-projects-claude-skill-owl/d62938b3-….jsonl`.
expecting: Fix = drop `cmd.current_dir(...)`, pass absolute path of `{self_id}.md` in the prompt, grant `--add-dir <psyche_dir>` so Read can still reach it.
next_action: Await human-verify checkpoint. Deploy, trigger Stop hook, confirm Psyche receives ECHO_COMMUNE message.

## Symptoms

expected: echo-commune subprocess spawned by Psyche wrapper should complete and terminate in a bounded time window.
actual: wrapper log ends at "[ECHO] spawned pid=23624" at 19:11:27; no further log entries; echo process (pid 23624) is no longer running at 19:19:27, yet wrapper never logged continuation (expected poll iteration 3).
errors: none explicit — silent hang of wrapper after spawning echo.
reproduction: run Psyche wrapper → first pulse completes → echo gate opens on next iteration → wrapper spawns echo commune → wrapper stops logging.
started: after commit f9fdea0 (2026-04-20 permission hardening) — prior behavior not observed hanging.

## Eliminated

- hypothesis: wrapper is still blocked on echo child handle (fire-and-forget leak)
  evidence: echo_fire.rs spawns via `spawn_detached_no_inherit` which returns immediately and only records pid. No join/wait. The wrapper is blocked in a DIFFERENT place — `poll_psyche` (20-min pulse window). That's expected, not a bug.
  timestamp: 2026-04-19T19:25:00

- hypothesis: wrapper crashed silently
  evidence: owl.exe pid 30320 (_psyche-wrapper doyle) still running. `poll` subprocess pid 12396 still running, started 19:11:27.379. Wrapper is blocked in poll_psyche (blocking spawn_capture_no_inherit) waiting for pulse interval (1200s) to elapse.
  timestamp: 2026-04-19T19:25:00

## Evidence

- timestamp: 2026-04-19T19:19:00
  checked: doyle.log contents
  found: Wrapper logged "[ECHO] spawned pid=23624" at 19:11:27 and then stopped logging entirely. No poll iteration 3 / no echo exit recorded.
  implication: Either (a) wrapper is blocked waiting on echo child handle, (b) wrapper crashed silently, or (c) fire-and-forget design doesn't log completion AND echo delivered nothing so no `[MSG] from=ECHO_COMMUNE` line either.

- timestamp: 2026-04-19T19:19:27
  checked: tasklist for pid 23624
  found: No task with PID 23624 running — echo subprocess has already exited.
  implication: Process terminated cleanly. No hang on the echo-commune side.

- timestamp: 2026-04-19T19:21:00
  checked: wmic process for owl.exe and poll
  found: owl.exe 30320 (_psyche-wrapper doyle) running; its child poll pid 12396 running, created 19:11:27.379. Pulse interval 1200s → next wake 19:31:27.
  implication: Wrapper is healthy, just blocked in normal poll. The "never terminated" symptom was a misread — the ECHO SUBPROCESS terminated promptly but WITHOUT delivering.

- timestamp: 2026-04-19T19:22:00
  checked: `~/.claude/projects/` for session UUID `d62938b3-319e-416e-8a65-6c409ace0d0a`
  found: Registered under `C--Users-decid-Documents-projects-claude-skill-owl/` (project path), NOT under `C--Users-decid-AppData-Local-spt-psyches-tracked/`.
  implication: CWD-based session lookup mismatch. Claude CLI resolves --resume via project-encoded subdirectory of ~/.claude/projects.

- timestamp: 2026-04-19T19:23:00
  checked: reproduce echo-commune spawn command from CWD=psyches/tracked
  found: `No conversation found with session ID: d62938b3-319e-416e-8a65-6c409ace0d0a` (< 1 s exit).
  implication: ROOT CAUSE confirmed via direct reproduction. `cmd.current_dir(owlery::psyche_dir())` (introduced f9fdea0) breaks --resume because the session UUID is not registered under that CWD's project encoding.

- timestamp: 2026-04-19T19:24:00
  checked: pre-f9fdea0 src/owl/echo_commune.rs via git show
  found: No `cmd.current_dir(...)` call before the commit. CWD inherited from the parent wrapper process (which inherits from `live start`, normally the project root).
  implication: Pre-hardening, `--resume` worked because CWD was the project root. Todlando's 2026-04-19T02:28 successful echo-commune delivery (pre-f9fdea0) corroborates.

- timestamp: 2026-04-19T19:30:00
  checked: cargo test --release --lib after fix
  found: 84 tests pass, 0 fail. Build warning-clean for echo_commune.rs.
  implication: Fix does not regress existing behavior.

## Resolution

root_cause: Commit f9fdea0 ("fix(260419-pwy): harden echo-commune subprocess permissions") added `cmd.current_dir(owlery::psyche_dir())` so haiku could Read `{self_id}.md` as a relative path. But Claude CLI's `-p --resume <uuid>` resolves resumable sessions via `~/.claude/projects/<CWD-encoded>/<uuid>.jsonl`. Setting CWD to `<SPT_HOME>/psyches/tracked/` makes Claude look under the WRONG project encoding, so it prints "No conversation found with session ID: …" to stderr (which is Stdio::null()-ed), exits non-zero with empty stdout. `wait_with_output()` returns successfully, `if stdout.is_empty() { return; }` takes the silent-bail branch, no `[COMMUNE]` marker is parsed, no `deliver_body_anonymous(&psyche_id, ...)` is called. Psyche's inbox stays empty and the wrapper waits forever for the (non-existent) delivery.

fix: src/owl/echo_commune.rs — (1) removed `cmd.current_dir(owlery::psyche_dir())` so CWD stays at the wrapper's inherited CWD (the project root where --resume can find the session). (2) Compute absolute path `{psyche_dir}/{self_id}.md` and bake it into the prompt so haiku reads the authoritative context file via absolute path, not relative. (3) Added `--add-dir <psyche_dir>` flag to grant Read/Grep/Glob access to the psyche tracked-context dir (outside CWD). (4) Updated top-of-file comment with a CWD INVARIANT paragraph documenting the anti-pattern so future changes do not regress this.

verification: (a) cargo build --release clean. (b) cargo test --release --lib: 84 pass / 0 fail. (c) Direct reproduction with the broken CWD confirms the failure mode ("No conversation found") in < 1 s. (d) Pending human-verify: deploy + trigger Stop → confirm `[MSG] from=ECHO_COMMUNE` line appears in doyle.log within ~30 s of the echo-commune spawn.

files_changed: ["src/owl/echo_commune.rs"]
