Skip to content

P2: LiveConversationView wipes REST-only session fields from the cache while mounted #655

Description

@RonenMars

Three handlers write the same React Query key with incompatible semantics, and one of them replaces the whole object.

Verified state

Checked 2026-08-12 against origin/main @ 86eeb2a1.

site operation effect on a field the WS frame omits
app/_layout.tsx:164-166 { ...prev, ...msg.session } — merge sticky: keeps the old value
components/conversation/LiveConversationView.tsx:143 qc.setQueryData(key, msg.session)whole-object replace destroyed
lib/eagerCacheSync.ts:25-31 { ...s, ...session } — merge sticky

effort, permissionMode and model are populated only on the REST path — parseStatusLine is called once, from handleGetSession (tb-streamer/src/server.ts:4522-4537), and never from the broadcast path, because managedToResponse is synchronous and holds no PTY handle.
So those fields are absent from every session_update frame, and the replace at LiveConversationView.tsx:143 wipes them from the cache for as long as the conversation screen is mounted.
Both handlers are subscribed simultaneously on the session detail screen, so whichever fires last wins.

Severity is bounded, but confirm before closing

The alarming version of this — the replace flipping deriveSessionPresentation out of its external_live or stale branch and changing what the badge says — does not apply.
Checked on the streamer side: all six session_update emit sites broadcast sessionStore.get(), whose managedToResponse carries lifecycle and ownership unconditionally (tb-streamer/src/session-store.ts:250, :272).
processLiveness has exactly one assignment site in the entire streamer (:325, hard-coded to "alive"), so the stale branch cannot be entered from a server signal at all.

That leaves this as lost metadata rather than a wrong label.

Why fix it anyway

A whole-object replace against a frame that is a strict subset of the REST shape is fragile regardless of today's field list.
It also interacts with planned work: merging is strictly better for the fields that exist now, and is simultaneously what would make a future liveness-sensitive field latch — which is why the agent sub-status design (threadbase-streamer#538) puts an explicit-null clearing contract on the server rather than relying on absence.
Land the merge with a comment saying so, rather than rediscovering the interaction later.

Found during review of the sub-status plan (threadbase-streamer#537); deliberately kept out of that feature's scope because it is independently correct.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Soon, but does not gate a releasebugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions