fix(client-runtime): stop resubscribing threads the server reports missing - #9822
fix(client-runtime): stop resubscribing threads the server reports missing#9822lnieuwenhuis wants to merge 7 commits into
Conversation
…ssing Subscribe failures carrying threadDisposition not-found now end the subscription terminally, tombstone the thread so foreground/probe wakeups never resubscribe, and drain queued persistence before cache removal so a debounced write cannot resurrect the deleted thread.
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This change modifies an existing production subscription path across the server protocol, client runtime, thread state, and cache persistence. It introduces terminal stream halting and concurrency-sensitive deletion ordering, so the runtime and wire-level effects are broader than a small isolated bug fix. Not approved because:
Review your spending limits in Billing settings. You can add or adjust custom eligibility rules. Learn more. |
…d miss Session replacements re-issued subscribeThread after a not-found tombstone because the terminal latch only filtered foreground wakeups while the outer session stream in subscribeDynamic stayed alive. Signal a halt Deferred from the terminalFailure handler and interrupt the outer session stream so no new subscribe issues; non-matching failures keep session-driven resubscription.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 0895455. Configure here.
A session replacement landing during the terminal handler's cache I/O started a new inner subscribe before the post-handle halt landed. Signal terminalHalt first so the outer session stream is already dead; the handler still drains as the running inner. Muse Spark (opencode)

Every foreground wakeup re-issues subscribeThread for a thread the server already reported missing, and the debounced persistence writer can resurrect the thread right after cache deletion.
Return a dedicated missing-thread error and stop the durable subscription across reconnects and foreground wakeups. Serialize cache saves and removal so queued or in-flight writes cannot restore a deleted thread. Other snapshot failures still retry.
Reimplements the intent of #8192 (which only drained the inner stream) on the current client-runtime layout, with regressions for single-attempt termination, no foreground resubscribe, and no persistence resurrection.
Built with muse-spark-1.3-contributor via OpenCode in T3 Code. Review follow-ups by GPT-6 via Codex.
Note
Medium Risk
Changes subscribeThread wire errors and client thread subscription, cache deletion, and persistence ordering; incorrect classification could stop sync early or leave stale cache.
Overview
Introduces
OrchestrationThreadNotFoundErroron thesubscribeThreadRPC and has the server emit it when a thread snapshot is unavailable (no replay fallback), instead of overloadingOrchestrationGetSnapshotError.On the client,
subscribeDynamicgains aterminalFailurepath that halts session- and wakeup-driven resubscribes after a classified failure. Thread sync treats not-found as terminal: it sets a tombstone latch, marks the thread deleted, removes cache, and does not retry on foreground wakeups or session replacement. Persistence is tightened with a lock, skipping writes after deletion, polling the debounced queue beforeremoveThread, so stale snapshots cannot resurrect a deleted thread.Other snapshot failures still use the existing retry and resubscribe behavior. Tests cover RPC decoding, terminal vs retriable failures, and deletion parity with
thread.deleted.Reviewed by Cursor Bugbot for commit 74ce349. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Stop resubscribing threads the server reports as missing
OrchestrationThreadNotFoundErrorto the contracts layer and thesubscribeThreadRPC error union; the server WebSocket layer now returns this typed error with the missingthreadIdinstead of a generic snapshot errorterminalFailureclassifier insubscribeDynamicthat halts all session-driven resubscription when every error in a cause matches the classifier, then runs a terminal handler oncemakeEnvironmentThreadStateclassifiesOrchestrationThreadNotFoundErroras terminal, marks the thread deleted viasetDeleted, and blocks foreground, probe, and session-replacement resubscriptions; generic snapshot errors remain retryablesetDeletednow drains the pending persistence queue and waits for an in-flight cache save before removing the cache entry; the persistence worker skips snapshots when the thread is already deletedsetDeletedin threads.ts acquires a persistence semaphore that serializes cache removal with saves — ifpersistblocks indefinitely on a cache write, deletion will also block; any caller that previously retried on a generic not-found message now needs to handle the typed error or it will fall into the ordinary retry pathMacroscope summarized 74ce349.