Skip to content

fix(threads): stop missing-thread subscription retries - #12711

Open
Quicksaver wants to merge 1 commit into
pingdotgg:mainfrom
Quicksaver:fix/thread-not-found-subscription-loop
Open

Quicksaver wants to merge 1 commit into
pingdotgg:mainfrom
Quicksaver:fix/thread-not-found-subscription-loop

Conversation

@Quicksaver

@Quicksaver Quicksaver commented Sep 20, 2026 •

Copy link
Copy Markdown

Summary

Deleted threads could trigger a subscription retry every 250 ms. The client treated an authoritative HTTP thread_not_found response as temporary, fell back to WebSocket, and retried when that snapshot failed too.

Missing-thread responses now clear cached detail, mark the thread deleted, and stop subscription retries, including after reconnecting or foregrounding the app. Other snapshot failures retain their fallback and retry behavior. Local web drafts wait for their server thread to exist before subscribing, so an expected pre-creation 404 cannot delete a valid draft.

Interactive demo - try it without building and installing

What changed

  • Recognize terminal missing-thread errors across HTTP and WebSocket snapshot loading.
  • Negotiate the dedicated WebSocket error through a versioned capability and legacy opt-in, preserving compatibility with older clients and servers.
  • Serialize cache removal with persistence across disposal and remount, preventing an in-flight save from restoring deleted detail.
  • Share draft readiness classification between web detail and status consumers, including drafts whose workspace mode changes before the first send.

Validation

245 focused tests passed; scoped typechecks and lint completed.
  • 235 tests passed across the nine changed client-runtime, contracts, and web test files, covering error classification, snapshot loading, cache persistence, subscription lifecycle, protocol compatibility, and draft readiness.
  • 10 server subscription tests passed, including missing-thread negotiation and buffered-event ordering. The other 199 tests in that file were excluded by the test-name filters.
  • Contracts and client-runtime typechecks passed.
  • Targeted lint on all 18 changed source and test files exited successfully with 26 warnings, including a React immutability warning in the draft subscription test and unused-variable/schema-compilation warnings in the server test file.
  • git diff --check upstream/main...HEAD passed.

🤖 Generated by GPT-6 in Codex via T3 Code

Summary by CodeRabbit

  • Bug Fixes

    • Missing threads are now recognized as permanently unavailable, stopping retries and removing stale cached details.
    • Thread deletions remain authoritative even when saves or updates are still in progress.
    • Local draft threads no longer start detail and status subscriptions before their server shell is ready.
    • Transient connection and server errors continue to use fallback and retry behavior.
  • Compatibility

    • Added versioned capability negotiation while preserving compatibility with older clients and servers.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Sep 20, 2026
Comment thread packages/client-runtime/src/state/threads.ts
@macroscopeapp

macroscopeapp Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This is a cross-package runtime change that alters WebSocket/HTTP error contracts, subscription retry behavior, cache deletion ordering, and draft readiness across existing paths. An unresolved medium-severity concurrency finding also concerns an old scope deleting a successor's cached thread after an ownership handoff.

Not approved because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: pingdotgg/t3code/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: e3c811f3-cbee-43c0-adac-0fc17eaa3d79

📥 Commits

Reviewing files that changed from the base of the PR and between 533af0c and d1cf1f9.

📒 Files selected for processing (9)
  • apps/server/src/server.test.ts
  • apps/server/src/ws.ts
  • packages/client-runtime/src/state/threadSnapshotHttp.ts
  • packages/client-runtime/src/state/threads-atoms.test.ts
  • packages/client-runtime/src/state/threads-pagination.test.ts
  • packages/client-runtime/src/state/threads.ts
  • packages/contracts/src/orchestration.ts
  • packages/contracts/src/rpc.test.ts
  • packages/contracts/src/rpc.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The change adds negotiated thread-not-found errors, preserves authoritative HTTP not-found failures, terminates missing-thread state, serializes cache deletion, and delays web subscriptions for local drafts until their server shells exist.

Changes

Thread synchronization reliability

Layer / File(s) Summary
Protocol negotiation and server errors
packages/contracts/src/orchestration.ts, packages/contracts/src/rpc.ts, packages/contracts/src/rpc.test.ts, apps/server/src/ws.ts, apps/server/src/server.test.ts, BRANCH_DETAILS.md
Subscribe-thread requests support versioned and legacy opt-in fields. Supported clients receive OrchestrationThreadNotFoundError; other clients retain OrchestrationGetSnapshotError. Contract tests cover payload and error compatibility.
Terminal error classification and HTTP loading
packages/client-runtime/src/errors/orchestration.ts, packages/client-runtime/src/errors/orchestration.test.ts, packages/client-runtime/src/state/threadSnapshotHttp.ts, packages/client-runtime/src/state/threadSnapshotHttp.test.ts
The client identifies terminal HTTP and WebSocket thread-not-found failures. The HTTP snapshot loader preserves authoritative not-found errors, while other failures use socket fallback.
Terminal deletion and cache persistence
packages/client-runtime/src/state/threads.ts, packages/client-runtime/src/state/threads-sync.test.ts, packages/client-runtime/src/state/threads-atoms.test.ts, packages/client-runtime/src/state/threads-pagination.test.ts
Missing threads become deleted, subscriptions stop, later events do not restore state, and cache removal is serialized with pending saves and resumed state owners. Tests cover deletion, pagination, and persistence races.
Draft-thread subscription readiness
apps/web/src/state/entities.ts, apps/web/src/components/ThreadRouteView.tsx, apps/web/src/state/entities.test.ts, apps/web/src/newThreadSubscriptionGate.test.ts, apps/web/src/composerDraftStore.test.ts
Local drafts are classified separately from server threads. Detail and status subscriptions wait for a server shell when required. Tests cover remote new-thread references and draft lookup across environment modes.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant ThreadState
  participant ThreadSnapshotLoader
  participant Server
  participant CacheStore
  ThreadState->>ThreadSnapshotLoader: load thread snapshot
  ThreadSnapshotLoader->>Server: request snapshot
  Server-->>ThreadSnapshotLoader: thread_not_found failure
  ThreadSnapshotLoader-->>ThreadState: terminal not-found error
  ThreadState->>CacheStore: remove cached thread
  ThreadState-->>Server: interrupt subscription
Loading

Suggested reviewers: bil0000, juliusmarminge, maria-rcks

Merge Risk: ⚪ Minimal · up to d1cf1

No confirmed issue currently prevents merging. Draft subscriptions do not start before successful server-thread creation; the cache-removal race remains unverified.

Security Architecture Review

Security architecture risk: 🔵 Low · up to d1cf1

The new protocol preserves older-client behavior and does not show a new authorization bypass. A narrow remount race may still discard locally cached thread detail; no server-side deletion is indicated.

Retained concerns

  • Medium · reliability · inferred: A terminal missing-thread response can start cache deletion under one owner, but deletion does not recheck ownership after waiting for the shared persistence lock. On a concurrent remount, an obsolete owner may remove the successor's locally persisted detail.
Security review details

Security Blast Radius

  • inferred — The identified ownership race affects cached detail for an environment and thread on the client; the inspected removal path does not delete a server thread. Its maximum runtime exposure is not established.

Security Findings and Attack Paths

  • observed — No authorization bypass is visible in the new error branch: it uses the existing scoped RPC and snapshot lookup. The prior generic missing-snapshot response already stated that the specified thread was not found.

Trust Boundaries and Controls

  • observed — The visible server control is an authenticated session with orchestration read scope. The snapshot query receives a thread ID but no visible session or tenant argument, so this source alone does not establish tenant isolation.

Resilience and Maintainability Implications

  • inferred — The shared lock prevents save and removal from overlapping, but without an ownership check inside the removal critical section it does not fully enforce successor ownership during remount.

Hardening Proposals

  • proposed — Revalidate resume-cache ownership while holding the persistence semaphore before removing detail, and cover disposal, remount, and delayed terminal-response ordering.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 24 functions across 18 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely identifies the main change: stopping retries for missing-thread subscriptions.
Description check ✅ Passed The description explains what changed, why the fix is needed, and how it was validated. It omits the template’s Checklist section and does not explicitly state whether UI changes apply, but it is othe…
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

Comment @coderabbitai help to get the list of available commands.

🤖 Co-authored by GPT-6 in Codex via T3 Code
@Quicksaver
Quicksaver force-pushed the fix/thread-not-found-subscription-loop branch from 533af0c to d1cf1f9 Compare September 27, 2026 16:44
@kvnloo

kvnloo commented Sep 27, 2026

Copy link
Copy Markdown

One small repo-hygiene note: BRANCH_DETAILS.md reads like implementation / merge notes for this PR rather than durable project documentation.

The root AGENTS.md explicitly says not to commit implementation plans, research notes, or PR summaries, and to let the merged PR be the implementation record.

Would it make sense to drop this file from the PR? The code and tests already capture the compatibility seams it describes.

@Quicksaver

Copy link
Copy Markdown
Author

@coderabbitai The retained ownership concern in your updated review is the same concern already assessed and resolved here.

I rechecked the current head, d1cf1f9. setDeletedLocked publishes deleted state through remember before waiting for persistenceLock. A successor inherits that deleted state, and persistence checks it inside the lock. It cannot save fresh detail in the proposed interleaving. Rechecking ownership before removal would instead let stale disk detail survive when the successor has already inherited deleted state.

All 70 tests in threads-atoms.test.ts, threads-sync.test.ts, and threads-pagination.test.ts pass on this head, including pending finalizer saves overlapping terminal deletion. No implementation change is needed for the repeated finding.

The generic docstring coverage warning also remains unchanged. It identifies no missing explanation, and the repository documentation policy asks us to document non-obvious constraints rather than add per-function narration. No documentation change is needed.

🤖 Generated by GPT-6 in Codex via T3 Code

@coderabbitai

coderabbitai Bot commented Sep 27, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
printf 'HEAD: '; git rev-parse --short HEAD
printf '\nFiles:\n'; fd -a '^(threads\.ts|AGENTS\.md)$' | head -60
printf '\nThread symbols:\n'; ast-grep outline packages/client-runtime/src/state/threads.ts --items all --match 'setDeletedLocked|remember|persistenceLock|persist|save|resume' | head -160
printf '\nRelevant source lines:\n'; rg -n -C 7 'setDeletedLocked|persistenceLock|remember\(|saveThread\(|removeThread\(|resum(e|ed)' packages/client-runtime/src/state/threads.ts | head -340
printf '\nPolicy matches:\n'; rg -n -C 4 'implementation plans|research notes|PR summaries|non-obvious|docstring|document' AGENTS.md | head -160

Length of output: 20223


🏁 Script executed:

#!/bin/bash
set -euo pipefail
printf 'State initialization, handoff and persistence:\n'
sed -n '185,335p' packages/client-runtime/src/state/threads.ts
printf '\nDeletion and stream gate:\n'
sed -n '390,475p' packages/client-runtime/src/state/threads.ts
printf '\nRegression test locations:\n'
rg -n -C 5 'pending finalizer|disposed successor|mounted successor|removeThread|persistenceLock' packages/client-runtime/src/state/threads-atoms.test.ts | tail -180
printf '\nRelated review text (if accessible):\n'
gh api repos/pingdotgg/t3code/issues/comments/5748829793 --jq '.body' 2>/dev/null | head -130 || true
gh api repos/pingdotgg/t3code/pulls/comments/4056962476 --jq '.body' 2>/dev/null | head -100 || true

Length of output: 19006


@Quicksaver, you are right. I checked the current head. setDeletedLocked publishes deleted state through remember before it waits for persistenceLock. A successor inherits that state, and persist checks it inside the lock before saving. My retained ownership concern was incorrect. A second ownership check before removal could leave stale cached detail on disk.

The generic docstring warning was also not actionable under the repository’s documentation policy. No code or documentation change is needed for either finding. I did not rerun the 70 tests you reported.

Thanks for the careful correction.


✏️ Learnings added
Learnt from: Quicksaver
URL: https://github.com/pingdotgg/t3code/pull/12711

Timestamp: 2026-09-27T17:25:43.708Z
Learning: In `packages/client-runtime/src/state/threads.ts`, `setDeletedLocked` publishes deleted state via `remember` before waiting for the shared `persistenceLock`. A successor owner inherits the deleted state, and `persist` checks that state inside the lock before saving. Do not flag removal by the previous owner after the lock wait as a successor-detail deletion race without a separate path that can persist successor detail. Rechecking ownership after the lock wait could leave stale cached detail on disk.

Learnt from: Quicksaver
URL: https://github.com/pingdotgg/t3code/pull/12711

Timestamp: 2026-09-27T17:25:43.708Z
Learning: The root `AGENTS.md` in pingdotgg/t3code directs contributors to document non-obvious constraints rather than narrate code or add per-function docstrings. Do not raise a generic docstring-coverage warning without identifying a specific missing explanation.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

@Quicksaver

Copy link
Copy Markdown
Author

@kvnloo In response to your note, this fork requires BRANCH_DETAILS.md as its per-branch customization contract. The fork's agent tooling reads it before working on the branch, so I am keeping it here as required branch documentation.

🤖 Generated by GPT-6 in Codex via T3 Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants