# Upgrade Guide — v0.28.65

<!-- bump: patch -->

## What Changed

Rapid-fire messages on the same Threadline thread no longer silently drop each other.

Previously, if two messages arrived on the same `threadId` within a tmux spawn window (~2-3s), both passed `PipeSessionSpawner.shouldUsePipeMode`, and each call to `PipeSessionSpawner.spawn` killed the prior tmux session for that thread (`tmux kill-session -t pipe-<threadId>`) before launching its own. Only the last message's session survived; earlier messages were destroyed mid-launch even though the relay had already reported `delivered: true` for them.

This release adds a new query method `PipeSessionSpawner.hasActiveSessionForThread(threadId)` and uses it as an additional guard in the relay Phase 2a pipe-mode gate (`src/commands/server.ts`). When an active pipe session already exists for the incoming `threadId`, the handler now falls through to the existing Phase 2b listener path (`ListenerSessionManager.writeToInbox`), which appends to `inbox.jsonl` under a file lock and serializes deliveries correctly.

The fix is purely additive:
- No change to the single-message-per-thread case (common case, zero behavioral change).
- No change to pipe-mode eligibility rules (trust, iqs, length, concurrency cap all unchanged).
- No new configuration field, no new API surface beyond the boolean query method.
- No migration, no data-format change.

## What to Tell Your User

- **No more lost messages in rapid-fire threads**: "If you send me several messages back-to-back in the same thread, I'll now handle them in order instead of only catching the last one."

## Summary of New Capabilities

| Capability | How to Use |
|-----------|-----------|
| Rapid-fire same-thread message serialization | automatic |

## Evidence

Reproduction path (the bug):
1. Two messages M1 and M2 arrive at the relay on the same `threadId` within ~2s.
2. M1 passes `shouldUsePipeMode` (no active sessions yet) → `spawn` launches tmux session `pipe-T`.
3. Before M1's session completes, M2 arrives. `shouldUsePipeMode` still returns eligible (no threadId check). `spawn` kills `pipe-T` (from M1) and launches its own.
4. M1's claude process dies mid-stream. User never receives M1's response. Relay reported `delivered: true` for both.

Observed symptom in field reports (cluster `cluster-threadline-relay-silently-drops-rapid-fire-messages-on-same`, 2 reporters at v0.28.30 and v0.28.35): "messages never received despite delivered acks." Re-verified present at v0.28.41 (AUT-5450-wo) and v0.28.64 (AUT-5855-wo) by direct source inspection of `PipeSessionSpawner.ts:209-231,260-268` and `server.ts:5688`.

Fixed-path verification (this release):
- `npm run build` emits `hasActiveSessionForThread` into `dist/threadline/PipeSessionSpawner.js` and the guard term into `dist/commands/server.js`.
- `PipeSessionSpawner.activeSessions` already tracks `threadId` per entry (line 311), so the new method is a pure read over existing state with no new invariants to maintain.
- When the guard fires, the handler enters the existing `listenerManager.writeToInbox` path (line 5721), whose append-under-lock behavior is the documented serial-queue for same-thread messages.

Integration-level verification (live relay with two rapid-fire same-thread messages) deferred as followup — the minimal fix is architectural (widen fall-through to the path that was already designed to handle this case). The existing unit suite for `PipeSessionSpawner` remains green post-change.
