---
status: resolved
trigger: "User-authored commune from doyle to doyle-psyche (2465 bytes, summarizing Phase 29 v1.10.0 ship, body literally contains the string `FIRE_ECHO_COMMUNE_NOW` while describing the Phase 29 deliverable) was intercepted by the psyche wrapper's FIRE_ECHO_COMMUNE_NOW control-message handler, failed payload parse, and was silently dropped. Never reached the `[MSG]` dispatch path, never written to claude --resume STDIN. doyle.log lines 60-65."
created: 2026-05-14T00:00:00Z
updated: 2026-05-14T00:00:00Z
---

## Current Focus

hypothesis (CONFIRMED): Two independent bugs in the Phase 29 FIRE_ECHO_COMMUNE_NOW control-message dispatch surface compose to silently drop legitimate user messages whose body contains the literal token `FIRE_ECHO_COMMUNE_NOW`:
- **Bug A — classifier overmatch**: predicate at `src/live/wrapper/mod.rs:164` is `msg.to_ascii_lowercase().contains("fire_echo_commune_now")`. Any payload anywhere in the body matches.
- **Bug B — silent drop on parse failure**: `handle_fire_echo_commune_arm` (lines 163-181) returns `true` whenever the predicate matched, regardless of parse outcome. Caller at lines 412-414 treats `true` as "handled" and `continue`s the inner-poll loop, so the message bypasses `resume_session_checked` (claude --resume STDIN) entirely. Drop log: line 177.

test: Trace the doyle.log evidence against the source. Confirmed:
- Line 164 predicate matches user commune body (which contains literal text `FIRE_ECHO_COMMUNE_NOW` while documenting the Phase 29 deliverable).
- Line 168 calls `parse_fire_echo_commune_now`, which requires the literal prefix `FIRE_ECHO_COMMUNE_NOW prior_session=` followed by a strict UUID — the user's prose-embedded mention does not match this shape, parse returns `None`.
- Line 176-178 arm logs `[FIRE-EC] payload parse failed; skipping` and returns `true`.
- Line 412-414 caller `continue`s.
- Result: message disappears. The 2465-byte body is never preserved.

expecting fix: tighten classifier to the emit-side wire contract AND make parse failure non-fatal (fall through to normal dispatch).

next_action: apply fix (per user instruction "Apply fix after root cause confirmed + checkpoint approved").

## Symptoms

expected: User commune from doyle delivered to doyle-psyche; wrapper poll picks it up; logs `[MSG] from=<EVENT type="msg" from="doyle">...`; body written to claude --resume STDIN; psyche processes the catch-up content as part of its next resume.
actual: doyle.log line 62 logs `[FIRE-EC] received FIRE_ECHO_COMMUNE_NOW control message`, line 63 logs `[FIRE-EC] payload parse failed; skipping`, no `[MSG]` line ever emitted for the 2465-byte body. Body never reaches claude --resume. Psyche has no awareness of the message.
errors: none surface — wrapper continues running, no panic, no stderr. The silent drop is the bug.
reproduction: any `$OWL deliver <self>-psyche <body>` where `<body>` contains the literal substring `FIRE_ECHO_COMMUNE_NOW` (case-insensitive) anywhere AND does NOT start with `FIRE_ECHO_COMMUNE_NOW prior_session={valid-uuid} forward_to_self={true|false} source={clear|compact}`. Trivially reproducible with: `$OWL deliver doyle-psyche <<<"discussing FIRE_ECHO_COMMUNE_NOW design"`.
started: Phase 29 Plan 03 (parse-side handler arm). v1.10.0 just shipped this.

## Eliminated

- BODY format double-encoding (parallel debug session `echo-commune-double-envelope.md`). Different surface — that one is about Self-inbox EVENT rendering for the wrapper's own emit. This one is about psyche-side INGEST classification dropping inbound user messages. No overlap.
- Owl deliver path failure: doyle.log line 61 confirms `poll returned 2465 bytes` — the message arrived at the psyche perch intact. Drop is post-poll, pre-resume.

## Evidence

- timestamp: 2026-05-14
  checked: C:\Users\decid\AppData\Local\spt\logs_latest\doyle.log lines 60-65
  found:
    ```
    [04:02:01] poll exited code=0 stderr=
    [04:02:01] poll returned 2465 bytes
    [04:02:01] [FIRE-EC] received FIRE_ECHO_COMMUNE_NOW control message
    [04:02:01] [FIRE-EC] payload parse failed; skipping
    [04:02:01] poll iteration 5 starting
    [04:02:01] [ECHO] gate rejected (sentinel fresh, 61s old); scheduling short pulse in 838s
    ```
  implication: 2465-byte body delivered to psyche perch, classifier matched it as control message, parser rejected it, arm returned true, caller continued. The body was never dispatched to claude --resume. Confirms both Bug A and Bug B simultaneously.

- timestamp: 2026-05-14
  checked: src/live/wrapper/mod.rs lines 163-181 (handle_fire_echo_commune_arm)
  found: predicate at line 164 is `msg.to_ascii_lowercase().contains("fire_echo_commune_now")`. No anchor on prefix, no shape check before commitment. On parse miss (line 176-178), arm logs "skipping" and returns `true` (signals "I handled this") to caller.
  implication: Bug A and Bug B located in same function. Both must be patched together.

- timestamp: 2026-05-14
  checked: src/live/wrapper/mod.rs lines 412-414 (caller in run() inner-poll)
  found: `if handle_fire_echo_commune_arm(self, &msg) { continue; }`. The `continue` is unconditional on arm return; arm `true` always means "skip remaining dispatch", including normal `[MSG]` / `resume_session_checked` path.
  implication: confirms drop semantics. Fix must either (a) make arm return `false` on parse miss so caller falls through, OR (b) move the fallthrough into the arm itself.

- timestamp: 2026-05-14
  checked: src/owl/plugin_session_start.rs lines 192-207 (build_fire_echo_commune_body — emit-side)
  found: wire shape is exactly `FIRE_ECHO_COMMUNE_NOW prior_session={uuid} forward_to_self=true source={clear|compact}`. Emitted via `crate::live::wrapper::FIRE_ECHO_COMMUNE_NOW_PREFIX` constant ("FIRE_ECHO_COMMUNE_NOW prior_session=").
  implication: A real control message ALWAYS begins with the literal prefix `FIRE_ECHO_COMMUNE_NOW prior_session=`. The classifier should anchor on this prefix (after stripping any outer EVENT wrap), not on a free-floating substring.

- timestamp: 2026-05-14
  checked: src/live/wrapper/mod.rs test_d_event_wrapped_body (lines 1175-1186)
  found: the parser is tested against `<EVENT type="msg" from="deployah">FIRE_ECHO_COMMUNE_NOW prior_session=...</EVENT>` — i.e. the prefix may sit immediately after a `>`. So an anchored prefix match must allow `FIRE_ECHO_COMMUNE_NOW prior_session=` either at the start of `msg` OR immediately after an EVENT envelope opening `>`.
  implication: a tighter classifier can use `FIRE_ECHO_COMMUNE_NOW_PREFIX` (the same constant the emitter uses) rather than the loose `contains("fire_echo_commune_now")`. Look for the prefix as a substring is still safe — user prose virtually never contains `FIRE_ECHO_COMMUNE_NOW prior_session=` together. Even tighter: check that what follows the prefix parses as a UUID-shape head.

## Specialist Review

(not invoked — Rust target, no skill in dispatch table)

## Resolution

### Root Cause

Two coupled defects in the Phase 29 Plan 03 FIRE_ECHO_COMMUNE_NOW handler at `src/live/wrapper/mod.rs`:

1. **Classifier overmatch (`handle_fire_echo_commune_arm` line 164)**: the gate predicate is a substring match on `fire_echo_commune_now` (case-insensitive). The emit-side wire constant is `FIRE_ECHO_COMMUNE_NOW prior_session=` — a stricter prefix the predicate should mirror. As written, any inbound message body that mentions the token (e.g. a user commune narrating the Phase 29 deliverable that just shipped) trips the gate.

2. **Drop-on-parse-failure (`handle_fire_echo_commune_arm` lines 176-180 + caller lines 412-414)**: on parse failure, the arm logs `[FIRE-EC] payload parse failed; skipping` and returns `true`. The caller treats `true` as "handled" and `continue`s the inner-poll loop, bypassing `resume_session_checked`. The message body is discarded — never written to claude --resume STDIN and never logged as `[MSG]`. Even with Bug A fixed, the arm's return-true-on-parse-miss is a defense-in-depth failure: any future false positive (or genuine corruption mid-flight) silently loses data.

### Fix

Patch `src/live/wrapper/mod.rs` `handle_fire_echo_commune_arm`:

- **Tighten the classifier**: replace `msg.to_ascii_lowercase().contains("fire_echo_commune_now")` with a match on the full emit-side prefix `FIRE_ECHO_COMMUNE_NOW prior_session=` (case-insensitive substring of the lowercased haystack against the lowercased prefix). This mirrors the emitter contract and is virtually impossible to hit accidentally in user prose. Reuse `FIRE_ECHO_COMMUNE_NOW_PREFIX` for the literal.

- **Fall through on parse failure**: when the predicate matches but `parse_fire_echo_commune_now` returns `None`, log the skip line AND log the body for forensic recoverability AND return `false`. Returning `false` signals "not handled here" to the caller, which then proceeds to the normal `[MSG]` / `resume_session_checked` path. This preserves the user's data on any future predicate false-positive.

Together: the predicate becomes anchored on the emit-side wire prefix (preventing the false positive that hit this commune), and parse failure becomes recoverable (so any future overmatch still doesn't lose data).

Tests to add:
- a free user-prose body that mentions `FIRE_ECHO_COMMUNE_NOW` (without the `prior_session=` suffix) must NOT trip the classifier — arm returns `false`.
- a body matching the prefix but with malformed payload (e.g. bad UUID) must log the skip AND return `false` so caller falls through.
- existing happy-path tests (test_a through test_n) must still pass unchanged.

### Verification

- `cargo test` — all existing fire-handler tests pass, new tests pass.
- Manual: deliver a body containing the literal `FIRE_ECHO_COMMUNE_NOW` in prose to a psyche perch; confirm log shows `[MSG]` line, not `[FIRE-EC] payload parse failed; skipping`.
- Manual: deliver a malformed control message (right prefix, bad UUID) to a psyche perch; confirm log shows both the skip line AND a `[MSG]` line (fallthrough).


### Commit

Applied in 05efb63 -- `fix(wrapper): tighten FIRE_ECHO_COMMUNE_NOW classifier + fallthrough on parse-miss`

- src/live/wrapper/mod.rs::handle_fire_echo_commune_arm: classifier now matches the full wire prefix `FIRE_ECHO_COMMUNE_NOW prior_session=` (case-insensitive) via the shared `FIRE_ECHO_COMMUNE_NOW_PREFIX` constant; parse-failure with matching prefix returns `false` so the caller falls through to the normal `[MSG]` / `resume_session_checked` path, preserving the body.
- Tests: test_d return-value contract flipped (parse-fail = false); test_e log-wording updated; test_h (user prose without `prior_session=` does NOT trip) and test_i (matching prefix + bad UUID falls through and logs `body_len=`) added as explicit regressions.
- cargo test --lib --test-threads=1: 321 pass / 0 fail. cargo build --release: clean.
