---
slug: psyche-final-note-not-logged
status: resolved
trigger: "Psyche ended its turn with a text note (e.g. 'All intentions cleared. Fresh slate saved.') but it was not surfaced in the wrapper log. Wrapper log shows only '[PSYCHE] resume (exit=0)' after the resume completes, followed immediately by 'poll iteration 2 starting' — the stdout/text content from Psyche's session is missing."
created: 2026-04-15
updated: 2026-04-15
resolved: 2026-04-15
---

# Debug: Psyche final note not logged by wrapper

## Symptoms

- **Expected behavior:** When Psyche finishes processing a commune and emits a text response (e.g. "All intentions cleared. Fresh slate saved.") as its final turn output, the wrapper log should surface that text so the user (and operators reading the log) can see what Psyche actually said.
- **Actual behavior:** Wrapper log shows only `[PSYCHE] resume (exit=0):` with no captured stdout body. Psyche's own log/UI shows the text was generated (screenshot confirms "All intentions cleared. Fresh slate saved." was Psyche's final message), but the wrapper did not relay or record it.
- **Error messages:** None. Exit code 0. Silent omission.
- **Timeline:** Observed on 2026-04-15 at ~00:52 PDT, during gen17 live session for `doyle` after commune 4 ("clear all active intentions").
- **Reproduction:**
  1. Have active live agent + Psyche pair (e.g. doyle / doyle-psyche).
  2. Send a commune to Psyche that does not require a [REPLY] or [NOTIFY] output marker.
  3. Psyche processes, emits a short text acknowledgment, turn ends with exit=0.
  4. Check wrapper log — text content absent.
- **Evidence (screenshots):**
  - `C:\Users\decid\Documents\ShareX\Screenshots\2026-04\WindowsTerminal_yoTKT6yM93.png` — Psyche's own terminal showing the "All intentions cleared. Fresh slate saved." final output + CONTEXT-SAVED line.
  - `C:\Users\decid\Documents\ShareX\Screenshots\2026-04\notepad++_3cWqp3FBPO.png` — wrapper log lines 11–19 showing MSG received, STDIN fed, `[PSYCHE] resume (exit=0)` with no body logged, then `poll iteration 2 starting`.
- **Live log evidence:** `~/.claude/spacetime/logs_latest/doyle.log` tail confirms exact trailing-colon header with no `>>>` body lines:

```
[00:52:36] [MSG] from=__REPLY_TO__:doyle: "Please clear..."
[00:52:36] [STDIN] 262 bytes to resume:
>>>  [Current time: 2026-04-15T00:52:36-07:00]
>>>  __REPLY_TO__:doyle
>>>  Please clear all active intentions...
[00:52:56] [PSYCHE] resume (exit=0):
[00:52:56] poll iteration 2 starting
```

## Context

- Phase 18.1 restricted Psyche tools to Read + Write + Edit and introduced output markers `[REPLY]` / `[NOTIFY]` for wrapper-handled delivery.
- When Psyche emits plain text with no marker, it's informational — but the wrapper currently appears to discard it instead of logging. The fresh-slate ack in this case was pure informational text.
- Related recent work: v1.7.0, UserPromptSubmit spool drain (commit cafc556), no-sleep-before-read lessons.

## Current Focus

- **hypothesis:** CONFIRMED — see Resolution.
- **test:** Inspect wrapper code path for `claude -p --resume` stdout and the actual log tail.
- **expecting:** Find a branch that extracts markers and discards the rest, OR a logger call that excludes stdout body.
- **next_action:** Apply fix to resume_session_checked and final_session.
- **reasoning_checkpoint:**
- **tdd_checkpoint:**

## Evidence

- timestamp: 2026-04-15T00:52:36 — wrapper received MSG from __REPLY_TO__:doyle (commune body)
- timestamp: 2026-04-15T00:52:36 — STDIN 262 bytes sent to resume
- timestamp: 2026-04-15T00:52:56 — `[PSYCHE] resume (exit=0):` (note trailing colon — non-empty-stdout branch taken)
- timestamp: 2026-04-15T00:52:56 — zero `>>>` body lines follow the header → `extract_claude_response(stdout)` returned an empty string
- timestamp: 2026-04-15T00:52:56 — Psyche's own screen output captured the text message + CONTEXT-SAVED, confirming text was generated
- Code diff: `init_session` (claude.rs:29-118) spawns claude with `--output-format json` (lines 54-55). `resume_session_checked` (claude.rs:204-283) and `final_session` (claude.rs:320-383) do NOT pass `--output-format json`.
- Code diff: both resume paths use `stderr(Stdio::null())`, so any output Claude CLI writes to stderr is discarded.

## Eliminated

- "Wrapper strips markers and drops the rest" — NOT the cause. `log_block` is called with the raw `response_text` returned by `extract_claude_response`, before any marker parsing. Marker parsing in `mod.rs:119` runs AFTER logging and doesn't mutate the logged string.

## Resolution

- **root_cause:** `resume_session_checked` / `resume_session` / `final_session` in `src/live/wrapper/claude.rs` deliberately do NOT pass `--output-format json` (keeps wrapper log blocks readable — pre-existing design choice). Under `-p --resume` without a format flag, claude CLI's default output ends up as either stream-json JSONL or plain text, or in some cases only whitespace on stdout with the real assistant message on stderr. The existing `extract_claude_response` handled whole-buffer JSON (`{result: "..."}`) and raw trim, but had no path for stream-json aggregation. Combined with `stderr(Stdio::null())` and a `log_block` that wrote its header unconditionally (even when body was empty), the net effect was a silent `[PSYCHE] resume (exit=0):` header with zero `>>>` body lines whenever the assistant output took any non-whole-JSON shape.
- **fix (applied):** In `src/live/wrapper/claude.rs`:
  1. `extract_claude_response` — now handles three shapes: (a) whole-buffer JSON with top-level `result` (init path), (b) stream-json JSONL, aggregating `{type:"assistant", message:{content:[{type:"text", text:...}]}}` events (and falling back to a terminal `{result:"..."}` line), (c) plain text via `output.trim()`. Returns `""` on truly-empty input so callers can distinguish.
  2. `log_block` — when body trims to empty, writes `>>>  (empty)` instead of the header-only pattern. Silent header-with-colon can no longer recur.
  3. All three resume sites (`resume_session`, `resume_session_checked`, `final_session`) switched `stderr(Stdio::null())` → `stderr(Stdio::piped())` and now log any non-empty stderr as a `[PSYCHE] resume stderr:` / `[PSYCHE] final stderr:` block. Future output-channel surprises surface instead of vanishing.
- **verification:** `cargo build --release` clean (8.00s, 3 pre-existing warnings). `cargo test --release` — all suites green (34+ tests across golden, native, result_variants). Binary deployed to `~/.claude/plugins/cache/cplugs/spt/1.7.0/owl.exe` and marketplace source copy. Next commune-reply from Psyche in any generation will either show text in a `>>>` body line (plain / stream-json / whole-JSON all handled), or show `(empty)` + a stderr block, or show a stderr block on its own — any silent case is now impossible by construction.
- **files_changed:** `src/live/wrapper/claude.rs` only (extract_claude_response rewrite, log_block empty-marker, stderr piping + logging at three sites).
