fix(client): self-heal empty thread details and back off failed thread subscriptions - #4405

Closed
colonelpanic8 wants to merge 2 commits into
pingdotgg:mainfrom
colonelpanic8:fix/thread-detail-load-hardening
Closed

fix(client): self-heal empty thread details and back off failed thread subscriptions#4405
colonelpanic8 wants to merge 2 commits into
pingdotgg:mainfrom
colonelpanic8:fix/thread-detail-load-hardening

Conversation

@colonelpanic8

@colonelpanic8colonelpanic8 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

What Changed

Two client-runtime fixes for thread views that can render empty forever:

  • Self-heal empty cached thread bodies.makeEnvironmentThreadState skipped the HTTP snapshot whenever any cached body existed and resumed the socket subscription from the cached snapshot sequence. If the cached body had no messages but a current sequence, the events that carried the messages were below the resume cursor and were never replayed — the thread rendered empty permanently, surviving app restarts. The subscription now reloads the HTTP snapshot whenever the cached body has no messages, and still degrades to today's sequence-resume when the loader yields nothing (offline/404).
  • Exponential backoff for expected subscription failures.subscribeDynamic's retryExpectedFailureAfter now also accepts (attempt: number) => Duration.Input, with the consecutive-failure counter reset by any delivered element. The thread subscription uses it: 250ms base, doubling, capped at 2s while the thread has no data and 10s once data exists. Existing plain-duration call sites are unchanged.

Why

An unsent composer draft subscribes to its pre-allocated thread id before the thread exists server-side (intentionally, so the subscription picks the thread up the moment the draft is submitted). With the flat 250ms retry, every open draft hot-looped an HTTP snapshot fetch + socket subscribe into a 404 four times per second, indefinitely. Measured before: steady ~4 req/s for as long as a draft stays open. Measured after: 250ms → 500ms → 1s → 2s steady (≤2s worst-case pickup after the draft is submitted; verified live that draft submission still promotes promptly and the created thread's messages appear).

The 2s/10s split matters because the draft-promotion flow renders the message timeline from the thread-detail subscription: a thread with no data yet may be created at any moment and must be picked up quickly, while a thread that already has data and keeps failing can back way off.

The empty-cache self-heal closes the loop for users who already have a poisoned cache: previously nothing ever re-fetched the full body, so the thread stayed empty forever.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Validation:

vp check # 0 errors
vp run typecheck # exit 0
vp test run packages/client-runtime/src/state/threads-sync.test.ts packages/client-runtime/src/rpc/client.test.ts # 28 passed

Plus a live pass in an isolated vp run dev environment: draft-open retry cadence measured at 250ms/500ms/1s/2s-steady (previously flat 250ms), draft submission still promotes and shows its messages, and existing threads render normally across reloads.


Note

Medium Risk
Changes thread sync and retry behavior for all thread detail subscriptions; wrong backoff or snapshot logic could delay live updates or add extra HTTP load, but scope is client-runtime only with broad test coverage.

Overview
Fixes thread views that could stay empty forever and stops draft subscriptions from hot-looping on 404s.

Empty cached thread bodies now trigger an HTTP snapshot reload before the live subscription resumes when the cached body has no messages, so replay does not skip message events below the cached sequence. If the loader returns nothing (offline/404), behavior falls back to resuming from the cached sequence as before.

Expected subscription failures use exponential backoff instead of a flat delay: retryExpectedFailureAfter accepts a duration or (attempt) => duration, consecutive failures increase the wait, and any delivered stream element resets the counter. Thread subscriptions use 250ms base doubling, capped at 2s without message data (draft / not-yet-created threads) and 10s once data exists.

Reviewed by Cursor Bugbot for commit e17b96e. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix thread subscriptions to self-heal empty thread details and back off on repeated failures

  • When a cached thread has no messages, the client now reloads the HTTP snapshot before resuming the live subscription, falling back to the cached sequence if the reload yields nothing (e.g. offline/404).
  • Replaces the fixed retryExpectedFailureAfter delay with an exponential backoff via retrySubscribeBackoff in threads.ts: capped at 2s when no data exists, 10s when cached data is present.
  • Extends SubscriptionOptions.retryExpectedFailureAfter in client.ts to accept a function (attempt: number) => Duration.Input, enabling per-attempt delay computation.
  • Backoff counter resets to zero whenever an element is delivered from the stream.
  • Behavioral Change: threads with empty cached message arrays now trigger an extra HTTP snapshot fetch before subscribing to live events.

Macroscope summarized e17b96e.

@coderabbitai

coderabbitaiBot commented Jul 23, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 75d93c73-b6dd-4ec1-a113-19f612b4dd5b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jul 23, 2026

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4659cf5. Configure here.

Comment threadpackages/client-runtime/src/state/threads.ts Outdated
@macroscopeapp

macroscopeappBot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR modifies runtime retry behavior for thread subscriptions, changing from fixed 250ms delays to exponential backoff with different caps based on data state. It also adds self-healing for empty cached threads. These are meaningful behavioral changes to client synchronization that warrant human review.

You can customize Macroscope's approvability policy. Learn more.

@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch 6 times, most recently from 7c86a4a to 10b95f1CompareJuly 25, 2026 03:23
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from 10b95f1 to e3aeb3aCompareJuly 27, 2026 11:14
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from e3aeb3a to ed92adaCompareJuly 28, 2026 02:07
colonelpanic8and others added 2 commits July 27, 2026 19:46
…d subscriptions
A thread view could render empty forever in two ways:
1. When a cached thread body existed, the client always skipped the HTTP
snapshot and resumed the socket subscription from the cached snapshot
sequence. A cached body with no messages but a current sequence would
resume past the events that carried the messages and never self-heal.
The subscription now reloads the HTTP snapshot whenever the cached
body has no messages, and still degrades to the sequence resume when
the loader yields nothing.
2. Expected subscription failures retried on a flat 250ms timer forever.
An unsent composer draft subscribes to its pre-allocated thread id
before the thread exists server-side, so every open draft hot-looped
a snapshot fetch plus socket subscribe into a 404 four times a second
indefinitely. Retries now back off exponentially from 250ms, capped
at 2s while the thread has no data (so a just-submitted draft's
thread is picked up promptly) and 10s once data exists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from ed92ada to e17b96eCompareJuly 28, 2026 02:51
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 10, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 10, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 11, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 18, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 28, 2026
@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

This branch overlaps the subscription repairs in #8192 and #8193. Those focused PRs are the active paths for missing-thread and retry behavior.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 31, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Sep 2, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Sep 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@colonelpanic8@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(client): self-heal empty thread details and back off failed thread subscriptions - #4405

Closed
colonelpanic8 wants to merge 2 commits into
pingdotgg:mainfrom
colonelpanic8:fix/thread-detail-load-hardening
Closed

fix(client): self-heal empty thread details and back off failed thread subscriptions#4405
colonelpanic8 wants to merge 2 commits into
pingdotgg:mainfrom
colonelpanic8:fix/thread-detail-load-hardening

Conversation

@colonelpanic8

@colonelpanic8colonelpanic8 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

What Changed

Two client-runtime fixes for thread views that can render empty forever:

  • Self-heal empty cached thread bodies.makeEnvironmentThreadState skipped the HTTP snapshot whenever any cached body existed and resumed the socket subscription from the cached snapshot sequence. If the cached body had no messages but a current sequence, the events that carried the messages were below the resume cursor and were never replayed — the thread rendered empty permanently, surviving app restarts. The subscription now reloads the HTTP snapshot whenever the cached body has no messages, and still degrades to today's sequence-resume when the loader yields nothing (offline/404).
  • Exponential backoff for expected subscription failures.subscribeDynamic's retryExpectedFailureAfter now also accepts (attempt: number) => Duration.Input, with the consecutive-failure counter reset by any delivered element. The thread subscription uses it: 250ms base, doubling, capped at 2s while the thread has no data and 10s once data exists. Existing plain-duration call sites are unchanged.

Why

An unsent composer draft subscribes to its pre-allocated thread id before the thread exists server-side (intentionally, so the subscription picks the thread up the moment the draft is submitted). With the flat 250ms retry, every open draft hot-looped an HTTP snapshot fetch + socket subscribe into a 404 four times per second, indefinitely. Measured before: steady ~4 req/s for as long as a draft stays open. Measured after: 250ms → 500ms → 1s → 2s steady (≤2s worst-case pickup after the draft is submitted; verified live that draft submission still promotes promptly and the created thread's messages appear).

The 2s/10s split matters because the draft-promotion flow renders the message timeline from the thread-detail subscription: a thread with no data yet may be created at any moment and must be picked up quickly, while a thread that already has data and keeps failing can back way off.

The empty-cache self-heal closes the loop for users who already have a poisoned cache: previously nothing ever re-fetched the full body, so the thread stayed empty forever.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Validation:

vp check # 0 errors
vp run typecheck # exit 0
vp test run packages/client-runtime/src/state/threads-sync.test.ts packages/client-runtime/src/rpc/client.test.ts # 28 passed

Plus a live pass in an isolated vp run dev environment: draft-open retry cadence measured at 250ms/500ms/1s/2s-steady (previously flat 250ms), draft submission still promotes and shows its messages, and existing threads render normally across reloads.


Note

Medium Risk
Changes thread sync and retry behavior for all thread detail subscriptions; wrong backoff or snapshot logic could delay live updates or add extra HTTP load, but scope is client-runtime only with broad test coverage.

Overview
Fixes thread views that could stay empty forever and stops draft subscriptions from hot-looping on 404s.

Empty cached thread bodies now trigger an HTTP snapshot reload before the live subscription resumes when the cached body has no messages, so replay does not skip message events below the cached sequence. If the loader returns nothing (offline/404), behavior falls back to resuming from the cached sequence as before.

Expected subscription failures use exponential backoff instead of a flat delay: retryExpectedFailureAfter accepts a duration or (attempt) => duration, consecutive failures increase the wait, and any delivered stream element resets the counter. Thread subscriptions use 250ms base doubling, capped at 2s without message data (draft / not-yet-created threads) and 10s once data exists.

Reviewed by Cursor Bugbot for commit e17b96e. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix thread subscriptions to self-heal empty thread details and back off on repeated failures

  • When a cached thread has no messages, the client now reloads the HTTP snapshot before resuming the live subscription, falling back to the cached sequence if the reload yields nothing (e.g. offline/404).
  • Replaces the fixed retryExpectedFailureAfter delay with an exponential backoff via retrySubscribeBackoff in threads.ts: capped at 2s when no data exists, 10s when cached data is present.
  • Extends SubscriptionOptions.retryExpectedFailureAfter in client.ts to accept a function (attempt: number) => Duration.Input, enabling per-attempt delay computation.
  • Backoff counter resets to zero whenever an element is delivered from the stream.
  • Behavioral Change: threads with empty cached message arrays now trigger an extra HTTP snapshot fetch before subscribing to live events.

Macroscope summarized e17b96e.

@coderabbitai

coderabbitaiBot commented Jul 23, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 75d93c73-b6dd-4ec1-a113-19f612b4dd5b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jul 23, 2026

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4659cf5. Configure here.

Comment threadpackages/client-runtime/src/state/threads.ts Outdated
@macroscopeapp

macroscopeappBot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR modifies runtime retry behavior for thread subscriptions, changing from fixed 250ms delays to exponential backoff with different caps based on data state. It also adds self-healing for empty cached threads. These are meaningful behavioral changes to client synchronization that warrant human review.

You can customize Macroscope's approvability policy. Learn more.

@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch 6 times, most recently from 7c86a4a to 10b95f1CompareJuly 25, 2026 03:23
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from 10b95f1 to e3aeb3aCompareJuly 27, 2026 11:14
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from e3aeb3a to ed92adaCompareJuly 28, 2026 02:07
colonelpanic8and others added 2 commits July 27, 2026 19:46
…d subscriptions
A thread view could render empty forever in two ways:
1. When a cached thread body existed, the client always skipped the HTTP
snapshot and resumed the socket subscription from the cached snapshot
sequence. A cached body with no messages but a current sequence would
resume past the events that carried the messages and never self-heal.
The subscription now reloads the HTTP snapshot whenever the cached
body has no messages, and still degrades to the sequence resume when
the loader yields nothing.
2. Expected subscription failures retried on a flat 250ms timer forever.
An unsent composer draft subscribes to its pre-allocated thread id
before the thread exists server-side, so every open draft hot-looped
a snapshot fetch plus socket subscribe into a 404 four times a second
indefinitely. Retries now back off exponentially from 250ms, capped
at 2s while the thread has no data (so a just-submitted draft's
thread is picked up promptly) and 10s once data exists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from ed92ada to e17b96eCompareJuly 28, 2026 02:51
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 10, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 10, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 11, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 18, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 28, 2026
@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

This branch overlaps the subscription repairs in #8192 and #8193. Those focused PRs are the active paths for missing-thread and retry behavior.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 31, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Sep 2, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Sep 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@colonelpanic8@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(client): self-heal empty thread details and back off failed thread subscriptions - #4405

Closed
colonelpanic8 wants to merge 2 commits into
pingdotgg:mainfrom
colonelpanic8:fix/thread-detail-load-hardening
Closed

fix(client): self-heal empty thread details and back off failed thread subscriptions#4405
colonelpanic8 wants to merge 2 commits into
pingdotgg:mainfrom
colonelpanic8:fix/thread-detail-load-hardening

Conversation

@colonelpanic8

@colonelpanic8colonelpanic8 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

What Changed

Two client-runtime fixes for thread views that can render empty forever:

  • Self-heal empty cached thread bodies.makeEnvironmentThreadState skipped the HTTP snapshot whenever any cached body existed and resumed the socket subscription from the cached snapshot sequence. If the cached body had no messages but a current sequence, the events that carried the messages were below the resume cursor and were never replayed — the thread rendered empty permanently, surviving app restarts. The subscription now reloads the HTTP snapshot whenever the cached body has no messages, and still degrades to today's sequence-resume when the loader yields nothing (offline/404).
  • Exponential backoff for expected subscription failures.subscribeDynamic's retryExpectedFailureAfter now also accepts (attempt: number) => Duration.Input, with the consecutive-failure counter reset by any delivered element. The thread subscription uses it: 250ms base, doubling, capped at 2s while the thread has no data and 10s once data exists. Existing plain-duration call sites are unchanged.

Why

An unsent composer draft subscribes to its pre-allocated thread id before the thread exists server-side (intentionally, so the subscription picks the thread up the moment the draft is submitted). With the flat 250ms retry, every open draft hot-looped an HTTP snapshot fetch + socket subscribe into a 404 four times per second, indefinitely. Measured before: steady ~4 req/s for as long as a draft stays open. Measured after: 250ms → 500ms → 1s → 2s steady (≤2s worst-case pickup after the draft is submitted; verified live that draft submission still promotes promptly and the created thread's messages appear).

The 2s/10s split matters because the draft-promotion flow renders the message timeline from the thread-detail subscription: a thread with no data yet may be created at any moment and must be picked up quickly, while a thread that already has data and keeps failing can back way off.

The empty-cache self-heal closes the loop for users who already have a poisoned cache: previously nothing ever re-fetched the full body, so the thread stayed empty forever.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Validation:

vp check # 0 errors
vp run typecheck # exit 0
vp test run packages/client-runtime/src/state/threads-sync.test.ts packages/client-runtime/src/rpc/client.test.ts # 28 passed

Plus a live pass in an isolated vp run dev environment: draft-open retry cadence measured at 250ms/500ms/1s/2s-steady (previously flat 250ms), draft submission still promotes and shows its messages, and existing threads render normally across reloads.


Note

Medium Risk
Changes thread sync and retry behavior for all thread detail subscriptions; wrong backoff or snapshot logic could delay live updates or add extra HTTP load, but scope is client-runtime only with broad test coverage.

Overview
Fixes thread views that could stay empty forever and stops draft subscriptions from hot-looping on 404s.

Empty cached thread bodies now trigger an HTTP snapshot reload before the live subscription resumes when the cached body has no messages, so replay does not skip message events below the cached sequence. If the loader returns nothing (offline/404), behavior falls back to resuming from the cached sequence as before.

Expected subscription failures use exponential backoff instead of a flat delay: retryExpectedFailureAfter accepts a duration or (attempt) => duration, consecutive failures increase the wait, and any delivered stream element resets the counter. Thread subscriptions use 250ms base doubling, capped at 2s without message data (draft / not-yet-created threads) and 10s once data exists.

Reviewed by Cursor Bugbot for commit e17b96e. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix thread subscriptions to self-heal empty thread details and back off on repeated failures

  • When a cached thread has no messages, the client now reloads the HTTP snapshot before resuming the live subscription, falling back to the cached sequence if the reload yields nothing (e.g. offline/404).
  • Replaces the fixed retryExpectedFailureAfter delay with an exponential backoff via retrySubscribeBackoff in threads.ts: capped at 2s when no data exists, 10s when cached data is present.
  • Extends SubscriptionOptions.retryExpectedFailureAfter in client.ts to accept a function (attempt: number) => Duration.Input, enabling per-attempt delay computation.
  • Backoff counter resets to zero whenever an element is delivered from the stream.
  • Behavioral Change: threads with empty cached message arrays now trigger an extra HTTP snapshot fetch before subscribing to live events.

Macroscope summarized e17b96e.

@coderabbitai

coderabbitaiBot commented Jul 23, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 75d93c73-b6dd-4ec1-a113-19f612b4dd5b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jul 23, 2026

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4659cf5. Configure here.

Comment threadpackages/client-runtime/src/state/threads.ts Outdated
@macroscopeapp

macroscopeappBot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR modifies runtime retry behavior for thread subscriptions, changing from fixed 250ms delays to exponential backoff with different caps based on data state. It also adds self-healing for empty cached threads. These are meaningful behavioral changes to client synchronization that warrant human review.

You can customize Macroscope's approvability policy. Learn more.

@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch 6 times, most recently from 7c86a4a to 10b95f1CompareJuly 25, 2026 03:23
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from 10b95f1 to e3aeb3aCompareJuly 27, 2026 11:14
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from e3aeb3a to ed92adaCompareJuly 28, 2026 02:07
colonelpanic8and others added 2 commits July 27, 2026 19:46
…d subscriptions
A thread view could render empty forever in two ways:
1. When a cached thread body existed, the client always skipped the HTTP
snapshot and resumed the socket subscription from the cached snapshot
sequence. A cached body with no messages but a current sequence would
resume past the events that carried the messages and never self-heal.
The subscription now reloads the HTTP snapshot whenever the cached
body has no messages, and still degrades to the sequence resume when
the loader yields nothing.
2. Expected subscription failures retried on a flat 250ms timer forever.
An unsent composer draft subscribes to its pre-allocated thread id
before the thread exists server-side, so every open draft hot-looped
a snapshot fetch plus socket subscribe into a 404 four times a second
indefinitely. Retries now back off exponentially from 250ms, capped
at 2s while the thread has no data (so a just-submitted draft's
thread is picked up promptly) and 10s once data exists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from ed92ada to e17b96eCompareJuly 28, 2026 02:51
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 10, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 10, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 11, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 18, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 28, 2026
@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

This branch overlaps the subscription repairs in #8192 and #8193. Those focused PRs are the active paths for missing-thread and retry behavior.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 31, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Sep 2, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Sep 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@colonelpanic8@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(client): self-heal empty thread details and back off failed thread subscriptions - #4405

Closed
colonelpanic8 wants to merge 2 commits into
pingdotgg:mainfrom
colonelpanic8:fix/thread-detail-load-hardening
Closed

fix(client): self-heal empty thread details and back off failed thread subscriptions#4405
colonelpanic8 wants to merge 2 commits into
pingdotgg:mainfrom
colonelpanic8:fix/thread-detail-load-hardening

Conversation

@colonelpanic8

@colonelpanic8colonelpanic8 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

What Changed

Two client-runtime fixes for thread views that can render empty forever:

  • Self-heal empty cached thread bodies.makeEnvironmentThreadState skipped the HTTP snapshot whenever any cached body existed and resumed the socket subscription from the cached snapshot sequence. If the cached body had no messages but a current sequence, the events that carried the messages were below the resume cursor and were never replayed — the thread rendered empty permanently, surviving app restarts. The subscription now reloads the HTTP snapshot whenever the cached body has no messages, and still degrades to today's sequence-resume when the loader yields nothing (offline/404).
  • Exponential backoff for expected subscription failures.subscribeDynamic's retryExpectedFailureAfter now also accepts (attempt: number) => Duration.Input, with the consecutive-failure counter reset by any delivered element. The thread subscription uses it: 250ms base, doubling, capped at 2s while the thread has no data and 10s once data exists. Existing plain-duration call sites are unchanged.

Why

An unsent composer draft subscribes to its pre-allocated thread id before the thread exists server-side (intentionally, so the subscription picks the thread up the moment the draft is submitted). With the flat 250ms retry, every open draft hot-looped an HTTP snapshot fetch + socket subscribe into a 404 four times per second, indefinitely. Measured before: steady ~4 req/s for as long as a draft stays open. Measured after: 250ms → 500ms → 1s → 2s steady (≤2s worst-case pickup after the draft is submitted; verified live that draft submission still promotes promptly and the created thread's messages appear).

The 2s/10s split matters because the draft-promotion flow renders the message timeline from the thread-detail subscription: a thread with no data yet may be created at any moment and must be picked up quickly, while a thread that already has data and keeps failing can back way off.

The empty-cache self-heal closes the loop for users who already have a poisoned cache: previously nothing ever re-fetched the full body, so the thread stayed empty forever.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Validation:

vp check # 0 errors
vp run typecheck # exit 0
vp test run packages/client-runtime/src/state/threads-sync.test.ts packages/client-runtime/src/rpc/client.test.ts # 28 passed

Plus a live pass in an isolated vp run dev environment: draft-open retry cadence measured at 250ms/500ms/1s/2s-steady (previously flat 250ms), draft submission still promotes and shows its messages, and existing threads render normally across reloads.


Note

Medium Risk
Changes thread sync and retry behavior for all thread detail subscriptions; wrong backoff or snapshot logic could delay live updates or add extra HTTP load, but scope is client-runtime only with broad test coverage.

Overview
Fixes thread views that could stay empty forever and stops draft subscriptions from hot-looping on 404s.

Empty cached thread bodies now trigger an HTTP snapshot reload before the live subscription resumes when the cached body has no messages, so replay does not skip message events below the cached sequence. If the loader returns nothing (offline/404), behavior falls back to resuming from the cached sequence as before.

Expected subscription failures use exponential backoff instead of a flat delay: retryExpectedFailureAfter accepts a duration or (attempt) => duration, consecutive failures increase the wait, and any delivered stream element resets the counter. Thread subscriptions use 250ms base doubling, capped at 2s without message data (draft / not-yet-created threads) and 10s once data exists.

Reviewed by Cursor Bugbot for commit e17b96e. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix thread subscriptions to self-heal empty thread details and back off on repeated failures

  • When a cached thread has no messages, the client now reloads the HTTP snapshot before resuming the live subscription, falling back to the cached sequence if the reload yields nothing (e.g. offline/404).
  • Replaces the fixed retryExpectedFailureAfter delay with an exponential backoff via retrySubscribeBackoff in threads.ts: capped at 2s when no data exists, 10s when cached data is present.
  • Extends SubscriptionOptions.retryExpectedFailureAfter in client.ts to accept a function (attempt: number) => Duration.Input, enabling per-attempt delay computation.
  • Backoff counter resets to zero whenever an element is delivered from the stream.
  • Behavioral Change: threads with empty cached message arrays now trigger an extra HTTP snapshot fetch before subscribing to live events.

Macroscope summarized e17b96e.

@coderabbitai

coderabbitaiBot commented Jul 23, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 75d93c73-b6dd-4ec1-a113-19f612b4dd5b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jul 23, 2026

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4659cf5. Configure here.

Comment threadpackages/client-runtime/src/state/threads.ts Outdated
@macroscopeapp

macroscopeappBot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR modifies runtime retry behavior for thread subscriptions, changing from fixed 250ms delays to exponential backoff with different caps based on data state. It also adds self-healing for empty cached threads. These are meaningful behavioral changes to client synchronization that warrant human review.

You can customize Macroscope's approvability policy. Learn more.

@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch 6 times, most recently from 7c86a4a to 10b95f1CompareJuly 25, 2026 03:23
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from 10b95f1 to e3aeb3aCompareJuly 27, 2026 11:14
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from e3aeb3a to ed92adaCompareJuly 28, 2026 02:07
colonelpanic8and others added 2 commits July 27, 2026 19:46
…d subscriptions
A thread view could render empty forever in two ways:
1. When a cached thread body existed, the client always skipped the HTTP
snapshot and resumed the socket subscription from the cached snapshot
sequence. A cached body with no messages but a current sequence would
resume past the events that carried the messages and never self-heal.
The subscription now reloads the HTTP snapshot whenever the cached
body has no messages, and still degrades to the sequence resume when
the loader yields nothing.
2. Expected subscription failures retried on a flat 250ms timer forever.
An unsent composer draft subscribes to its pre-allocated thread id
before the thread exists server-side, so every open draft hot-looped
a snapshot fetch plus socket subscribe into a 404 four times a second
indefinitely. Retries now back off exponentially from 250ms, capped
at 2s while the thread has no data (so a just-submitted draft's
thread is picked up promptly) and 10s once data exists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from ed92ada to e17b96eCompareJuly 28, 2026 02:51
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 10, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 10, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 11, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 18, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 28, 2026
@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

This branch overlaps the subscription repairs in #8192 and #8193. Those focused PRs are the active paths for missing-thread and retry behavior.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 31, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Sep 2, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Sep 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@colonelpanic8@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(client): self-heal empty thread details and back off failed thread subscriptions - #4405

Closed
colonelpanic8 wants to merge 2 commits into
pingdotgg:mainfrom
colonelpanic8:fix/thread-detail-load-hardening
Closed

fix(client): self-heal empty thread details and back off failed thread subscriptions#4405
colonelpanic8 wants to merge 2 commits into
pingdotgg:mainfrom
colonelpanic8:fix/thread-detail-load-hardening

Conversation

@colonelpanic8

@colonelpanic8colonelpanic8 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

What Changed

Two client-runtime fixes for thread views that can render empty forever:

  • Self-heal empty cached thread bodies.makeEnvironmentThreadState skipped the HTTP snapshot whenever any cached body existed and resumed the socket subscription from the cached snapshot sequence. If the cached body had no messages but a current sequence, the events that carried the messages were below the resume cursor and were never replayed — the thread rendered empty permanently, surviving app restarts. The subscription now reloads the HTTP snapshot whenever the cached body has no messages, and still degrades to today's sequence-resume when the loader yields nothing (offline/404).
  • Exponential backoff for expected subscription failures.subscribeDynamic's retryExpectedFailureAfter now also accepts (attempt: number) => Duration.Input, with the consecutive-failure counter reset by any delivered element. The thread subscription uses it: 250ms base, doubling, capped at 2s while the thread has no data and 10s once data exists. Existing plain-duration call sites are unchanged.

Why

An unsent composer draft subscribes to its pre-allocated thread id before the thread exists server-side (intentionally, so the subscription picks the thread up the moment the draft is submitted). With the flat 250ms retry, every open draft hot-looped an HTTP snapshot fetch + socket subscribe into a 404 four times per second, indefinitely. Measured before: steady ~4 req/s for as long as a draft stays open. Measured after: 250ms → 500ms → 1s → 2s steady (≤2s worst-case pickup after the draft is submitted; verified live that draft submission still promotes promptly and the created thread's messages appear).

The 2s/10s split matters because the draft-promotion flow renders the message timeline from the thread-detail subscription: a thread with no data yet may be created at any moment and must be picked up quickly, while a thread that already has data and keeps failing can back way off.

The empty-cache self-heal closes the loop for users who already have a poisoned cache: previously nothing ever re-fetched the full body, so the thread stayed empty forever.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Validation:

vp check # 0 errors
vp run typecheck # exit 0
vp test run packages/client-runtime/src/state/threads-sync.test.ts packages/client-runtime/src/rpc/client.test.ts # 28 passed

Plus a live pass in an isolated vp run dev environment: draft-open retry cadence measured at 250ms/500ms/1s/2s-steady (previously flat 250ms), draft submission still promotes and shows its messages, and existing threads render normally across reloads.


Note

Medium Risk
Changes thread sync and retry behavior for all thread detail subscriptions; wrong backoff or snapshot logic could delay live updates or add extra HTTP load, but scope is client-runtime only with broad test coverage.

Overview
Fixes thread views that could stay empty forever and stops draft subscriptions from hot-looping on 404s.

Empty cached thread bodies now trigger an HTTP snapshot reload before the live subscription resumes when the cached body has no messages, so replay does not skip message events below the cached sequence. If the loader returns nothing (offline/404), behavior falls back to resuming from the cached sequence as before.

Expected subscription failures use exponential backoff instead of a flat delay: retryExpectedFailureAfter accepts a duration or (attempt) => duration, consecutive failures increase the wait, and any delivered stream element resets the counter. Thread subscriptions use 250ms base doubling, capped at 2s without message data (draft / not-yet-created threads) and 10s once data exists.

Reviewed by Cursor Bugbot for commit e17b96e. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix thread subscriptions to self-heal empty thread details and back off on repeated failures

  • When a cached thread has no messages, the client now reloads the HTTP snapshot before resuming the live subscription, falling back to the cached sequence if the reload yields nothing (e.g. offline/404).
  • Replaces the fixed retryExpectedFailureAfter delay with an exponential backoff via retrySubscribeBackoff in threads.ts: capped at 2s when no data exists, 10s when cached data is present.
  • Extends SubscriptionOptions.retryExpectedFailureAfter in client.ts to accept a function (attempt: number) => Duration.Input, enabling per-attempt delay computation.
  • Backoff counter resets to zero whenever an element is delivered from the stream.
  • Behavioral Change: threads with empty cached message arrays now trigger an extra HTTP snapshot fetch before subscribing to live events.

Macroscope summarized e17b96e.

@coderabbitai

coderabbitaiBot commented Jul 23, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 75d93c73-b6dd-4ec1-a113-19f612b4dd5b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jul 23, 2026

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4659cf5. Configure here.

Comment threadpackages/client-runtime/src/state/threads.ts Outdated
@macroscopeapp

macroscopeappBot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR modifies runtime retry behavior for thread subscriptions, changing from fixed 250ms delays to exponential backoff with different caps based on data state. It also adds self-healing for empty cached threads. These are meaningful behavioral changes to client synchronization that warrant human review.

You can customize Macroscope's approvability policy. Learn more.

@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch 6 times, most recently from 7c86a4a to 10b95f1CompareJuly 25, 2026 03:23
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from 10b95f1 to e3aeb3aCompareJuly 27, 2026 11:14
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from e3aeb3a to ed92adaCompareJuly 28, 2026 02:07
colonelpanic8and others added 2 commits July 27, 2026 19:46
…d subscriptions
A thread view could render empty forever in two ways:
1. When a cached thread body existed, the client always skipped the HTTP
snapshot and resumed the socket subscription from the cached snapshot
sequence. A cached body with no messages but a current sequence would
resume past the events that carried the messages and never self-heal.
The subscription now reloads the HTTP snapshot whenever the cached
body has no messages, and still degrades to the sequence resume when
the loader yields nothing.
2. Expected subscription failures retried on a flat 250ms timer forever.
An unsent composer draft subscribes to its pre-allocated thread id
before the thread exists server-side, so every open draft hot-looped
a snapshot fetch plus socket subscribe into a 404 four times a second
indefinitely. Retries now back off exponentially from 250ms, capped
at 2s while the thread has no data (so a just-submitted draft's
thread is picked up promptly) and 10s once data exists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from ed92ada to e17b96eCompareJuly 28, 2026 02:51
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 10, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 10, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 11, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 18, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 28, 2026
@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

This branch overlaps the subscription repairs in #8192 and #8193. Those focused PRs are the active paths for missing-thread and retry behavior.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 31, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Sep 2, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Sep 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@colonelpanic8@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(client): self-heal empty thread details and back off failed thread subscriptions - #4405

Closed
colonelpanic8 wants to merge 2 commits into
pingdotgg:mainfrom
colonelpanic8:fix/thread-detail-load-hardening
Closed

fix(client): self-heal empty thread details and back off failed thread subscriptions#4405
colonelpanic8 wants to merge 2 commits into
pingdotgg:mainfrom
colonelpanic8:fix/thread-detail-load-hardening

Conversation

@colonelpanic8

@colonelpanic8colonelpanic8 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

What Changed

Two client-runtime fixes for thread views that can render empty forever:

  • Self-heal empty cached thread bodies.makeEnvironmentThreadState skipped the HTTP snapshot whenever any cached body existed and resumed the socket subscription from the cached snapshot sequence. If the cached body had no messages but a current sequence, the events that carried the messages were below the resume cursor and were never replayed — the thread rendered empty permanently, surviving app restarts. The subscription now reloads the HTTP snapshot whenever the cached body has no messages, and still degrades to today's sequence-resume when the loader yields nothing (offline/404).
  • Exponential backoff for expected subscription failures.subscribeDynamic's retryExpectedFailureAfter now also accepts (attempt: number) => Duration.Input, with the consecutive-failure counter reset by any delivered element. The thread subscription uses it: 250ms base, doubling, capped at 2s while the thread has no data and 10s once data exists. Existing plain-duration call sites are unchanged.

Why

An unsent composer draft subscribes to its pre-allocated thread id before the thread exists server-side (intentionally, so the subscription picks the thread up the moment the draft is submitted). With the flat 250ms retry, every open draft hot-looped an HTTP snapshot fetch + socket subscribe into a 404 four times per second, indefinitely. Measured before: steady ~4 req/s for as long as a draft stays open. Measured after: 250ms → 500ms → 1s → 2s steady (≤2s worst-case pickup after the draft is submitted; verified live that draft submission still promotes promptly and the created thread's messages appear).

The 2s/10s split matters because the draft-promotion flow renders the message timeline from the thread-detail subscription: a thread with no data yet may be created at any moment and must be picked up quickly, while a thread that already has data and keeps failing can back way off.

The empty-cache self-heal closes the loop for users who already have a poisoned cache: previously nothing ever re-fetched the full body, so the thread stayed empty forever.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Validation:

vp check # 0 errors
vp run typecheck # exit 0
vp test run packages/client-runtime/src/state/threads-sync.test.ts packages/client-runtime/src/rpc/client.test.ts # 28 passed

Plus a live pass in an isolated vp run dev environment: draft-open retry cadence measured at 250ms/500ms/1s/2s-steady (previously flat 250ms), draft submission still promotes and shows its messages, and existing threads render normally across reloads.


Note

Medium Risk
Changes thread sync and retry behavior for all thread detail subscriptions; wrong backoff or snapshot logic could delay live updates or add extra HTTP load, but scope is client-runtime only with broad test coverage.

Overview
Fixes thread views that could stay empty forever and stops draft subscriptions from hot-looping on 404s.

Empty cached thread bodies now trigger an HTTP snapshot reload before the live subscription resumes when the cached body has no messages, so replay does not skip message events below the cached sequence. If the loader returns nothing (offline/404), behavior falls back to resuming from the cached sequence as before.

Expected subscription failures use exponential backoff instead of a flat delay: retryExpectedFailureAfter accepts a duration or (attempt) => duration, consecutive failures increase the wait, and any delivered stream element resets the counter. Thread subscriptions use 250ms base doubling, capped at 2s without message data (draft / not-yet-created threads) and 10s once data exists.

Reviewed by Cursor Bugbot for commit e17b96e. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix thread subscriptions to self-heal empty thread details and back off on repeated failures

  • When a cached thread has no messages, the client now reloads the HTTP snapshot before resuming the live subscription, falling back to the cached sequence if the reload yields nothing (e.g. offline/404).
  • Replaces the fixed retryExpectedFailureAfter delay with an exponential backoff via retrySubscribeBackoff in threads.ts: capped at 2s when no data exists, 10s when cached data is present.
  • Extends SubscriptionOptions.retryExpectedFailureAfter in client.ts to accept a function (attempt: number) => Duration.Input, enabling per-attempt delay computation.
  • Backoff counter resets to zero whenever an element is delivered from the stream.
  • Behavioral Change: threads with empty cached message arrays now trigger an extra HTTP snapshot fetch before subscribing to live events.

Macroscope summarized e17b96e.

@coderabbitai

coderabbitaiBot commented Jul 23, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 75d93c73-b6dd-4ec1-a113-19f612b4dd5b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jul 23, 2026

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4659cf5. Configure here.

Comment threadpackages/client-runtime/src/state/threads.ts Outdated
@macroscopeapp

macroscopeappBot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR modifies runtime retry behavior for thread subscriptions, changing from fixed 250ms delays to exponential backoff with different caps based on data state. It also adds self-healing for empty cached threads. These are meaningful behavioral changes to client synchronization that warrant human review.

You can customize Macroscope's approvability policy. Learn more.

@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch 6 times, most recently from 7c86a4a to 10b95f1CompareJuly 25, 2026 03:23
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from 10b95f1 to e3aeb3aCompareJuly 27, 2026 11:14
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from e3aeb3a to ed92adaCompareJuly 28, 2026 02:07
colonelpanic8and others added 2 commits July 27, 2026 19:46
…d subscriptions
A thread view could render empty forever in two ways:
1. When a cached thread body existed, the client always skipped the HTTP
snapshot and resumed the socket subscription from the cached snapshot
sequence. A cached body with no messages but a current sequence would
resume past the events that carried the messages and never self-heal.
The subscription now reloads the HTTP snapshot whenever the cached
body has no messages, and still degrades to the sequence resume when
the loader yields nothing.
2. Expected subscription failures retried on a flat 250ms timer forever.
An unsent composer draft subscribes to its pre-allocated thread id
before the thread exists server-side, so every open draft hot-looped
a snapshot fetch plus socket subscribe into a 404 four times a second
indefinitely. Retries now back off exponentially from 250ms, capped
at 2s while the thread has no data (so a just-submitted draft's
thread is picked up promptly) and 10s once data exists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from ed92ada to e17b96eCompareJuly 28, 2026 02:51
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 10, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 10, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 11, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 18, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 28, 2026
@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

This branch overlaps the subscription repairs in #8192 and #8193. Those focused PRs are the active paths for missing-thread and retry behavior.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 31, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Sep 2, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Sep 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@colonelpanic8@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(client): self-heal empty thread details and back off failed thread subscriptions - #4405

Closed
colonelpanic8 wants to merge 2 commits into
pingdotgg:mainfrom
colonelpanic8:fix/thread-detail-load-hardening
Closed

fix(client): self-heal empty thread details and back off failed thread subscriptions#4405
colonelpanic8 wants to merge 2 commits into
pingdotgg:mainfrom
colonelpanic8:fix/thread-detail-load-hardening

Conversation

@colonelpanic8

@colonelpanic8colonelpanic8 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

What Changed

Two client-runtime fixes for thread views that can render empty forever:

  • Self-heal empty cached thread bodies.makeEnvironmentThreadState skipped the HTTP snapshot whenever any cached body existed and resumed the socket subscription from the cached snapshot sequence. If the cached body had no messages but a current sequence, the events that carried the messages were below the resume cursor and were never replayed — the thread rendered empty permanently, surviving app restarts. The subscription now reloads the HTTP snapshot whenever the cached body has no messages, and still degrades to today's sequence-resume when the loader yields nothing (offline/404).
  • Exponential backoff for expected subscription failures.subscribeDynamic's retryExpectedFailureAfter now also accepts (attempt: number) => Duration.Input, with the consecutive-failure counter reset by any delivered element. The thread subscription uses it: 250ms base, doubling, capped at 2s while the thread has no data and 10s once data exists. Existing plain-duration call sites are unchanged.

Why

An unsent composer draft subscribes to its pre-allocated thread id before the thread exists server-side (intentionally, so the subscription picks the thread up the moment the draft is submitted). With the flat 250ms retry, every open draft hot-looped an HTTP snapshot fetch + socket subscribe into a 404 four times per second, indefinitely. Measured before: steady ~4 req/s for as long as a draft stays open. Measured after: 250ms → 500ms → 1s → 2s steady (≤2s worst-case pickup after the draft is submitted; verified live that draft submission still promotes promptly and the created thread's messages appear).

The 2s/10s split matters because the draft-promotion flow renders the message timeline from the thread-detail subscription: a thread with no data yet may be created at any moment and must be picked up quickly, while a thread that already has data and keeps failing can back way off.

The empty-cache self-heal closes the loop for users who already have a poisoned cache: previously nothing ever re-fetched the full body, so the thread stayed empty forever.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Validation:

vp check # 0 errors
vp run typecheck # exit 0
vp test run packages/client-runtime/src/state/threads-sync.test.ts packages/client-runtime/src/rpc/client.test.ts # 28 passed

Plus a live pass in an isolated vp run dev environment: draft-open retry cadence measured at 250ms/500ms/1s/2s-steady (previously flat 250ms), draft submission still promotes and shows its messages, and existing threads render normally across reloads.


Note

Medium Risk
Changes thread sync and retry behavior for all thread detail subscriptions; wrong backoff or snapshot logic could delay live updates or add extra HTTP load, but scope is client-runtime only with broad test coverage.

Overview
Fixes thread views that could stay empty forever and stops draft subscriptions from hot-looping on 404s.

Empty cached thread bodies now trigger an HTTP snapshot reload before the live subscription resumes when the cached body has no messages, so replay does not skip message events below the cached sequence. If the loader returns nothing (offline/404), behavior falls back to resuming from the cached sequence as before.

Expected subscription failures use exponential backoff instead of a flat delay: retryExpectedFailureAfter accepts a duration or (attempt) => duration, consecutive failures increase the wait, and any delivered stream element resets the counter. Thread subscriptions use 250ms base doubling, capped at 2s without message data (draft / not-yet-created threads) and 10s once data exists.

Reviewed by Cursor Bugbot for commit e17b96e. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix thread subscriptions to self-heal empty thread details and back off on repeated failures

  • When a cached thread has no messages, the client now reloads the HTTP snapshot before resuming the live subscription, falling back to the cached sequence if the reload yields nothing (e.g. offline/404).
  • Replaces the fixed retryExpectedFailureAfter delay with an exponential backoff via retrySubscribeBackoff in threads.ts: capped at 2s when no data exists, 10s when cached data is present.
  • Extends SubscriptionOptions.retryExpectedFailureAfter in client.ts to accept a function (attempt: number) => Duration.Input, enabling per-attempt delay computation.
  • Backoff counter resets to zero whenever an element is delivered from the stream.
  • Behavioral Change: threads with empty cached message arrays now trigger an extra HTTP snapshot fetch before subscribing to live events.

Macroscope summarized e17b96e.

@coderabbitai

coderabbitaiBot commented Jul 23, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 75d93c73-b6dd-4ec1-a113-19f612b4dd5b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jul 23, 2026

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4659cf5. Configure here.

Comment threadpackages/client-runtime/src/state/threads.ts Outdated
@macroscopeapp

macroscopeappBot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR modifies runtime retry behavior for thread subscriptions, changing from fixed 250ms delays to exponential backoff with different caps based on data state. It also adds self-healing for empty cached threads. These are meaningful behavioral changes to client synchronization that warrant human review.

You can customize Macroscope's approvability policy. Learn more.

@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch 6 times, most recently from 7c86a4a to 10b95f1CompareJuly 25, 2026 03:23
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from 10b95f1 to e3aeb3aCompareJuly 27, 2026 11:14
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from e3aeb3a to ed92adaCompareJuly 28, 2026 02:07
colonelpanic8and others added 2 commits July 27, 2026 19:46
…d subscriptions
A thread view could render empty forever in two ways:
1. When a cached thread body existed, the client always skipped the HTTP
snapshot and resumed the socket subscription from the cached snapshot
sequence. A cached body with no messages but a current sequence would
resume past the events that carried the messages and never self-heal.
The subscription now reloads the HTTP snapshot whenever the cached
body has no messages, and still degrades to the sequence resume when
the loader yields nothing.
2. Expected subscription failures retried on a flat 250ms timer forever.
An unsent composer draft subscribes to its pre-allocated thread id
before the thread exists server-side, so every open draft hot-looped
a snapshot fetch plus socket subscribe into a 404 four times a second
indefinitely. Retries now back off exponentially from 250ms, capped
at 2s while the thread has no data (so a just-submitted draft's
thread is picked up promptly) and 10s once data exists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from ed92ada to e17b96eCompareJuly 28, 2026 02:51
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 10, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 10, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 11, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 18, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 28, 2026
@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

This branch overlaps the subscription repairs in #8192 and #8193. Those focused PRs are the active paths for missing-thread and retry behavior.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 31, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Sep 2, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Sep 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@colonelpanic8@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(client): self-heal empty thread details and back off failed thread subscriptions - #4405

Closed
colonelpanic8 wants to merge 2 commits into
pingdotgg:mainfrom
colonelpanic8:fix/thread-detail-load-hardening
Closed

fix(client): self-heal empty thread details and back off failed thread subscriptions#4405
colonelpanic8 wants to merge 2 commits into
pingdotgg:mainfrom
colonelpanic8:fix/thread-detail-load-hardening

Conversation

@colonelpanic8

@colonelpanic8colonelpanic8 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

What Changed

Two client-runtime fixes for thread views that can render empty forever:

  • Self-heal empty cached thread bodies.makeEnvironmentThreadState skipped the HTTP snapshot whenever any cached body existed and resumed the socket subscription from the cached snapshot sequence. If the cached body had no messages but a current sequence, the events that carried the messages were below the resume cursor and were never replayed — the thread rendered empty permanently, surviving app restarts. The subscription now reloads the HTTP snapshot whenever the cached body has no messages, and still degrades to today's sequence-resume when the loader yields nothing (offline/404).
  • Exponential backoff for expected subscription failures.subscribeDynamic's retryExpectedFailureAfter now also accepts (attempt: number) => Duration.Input, with the consecutive-failure counter reset by any delivered element. The thread subscription uses it: 250ms base, doubling, capped at 2s while the thread has no data and 10s once data exists. Existing plain-duration call sites are unchanged.

Why

An unsent composer draft subscribes to its pre-allocated thread id before the thread exists server-side (intentionally, so the subscription picks the thread up the moment the draft is submitted). With the flat 250ms retry, every open draft hot-looped an HTTP snapshot fetch + socket subscribe into a 404 four times per second, indefinitely. Measured before: steady ~4 req/s for as long as a draft stays open. Measured after: 250ms → 500ms → 1s → 2s steady (≤2s worst-case pickup after the draft is submitted; verified live that draft submission still promotes promptly and the created thread's messages appear).

The 2s/10s split matters because the draft-promotion flow renders the message timeline from the thread-detail subscription: a thread with no data yet may be created at any moment and must be picked up quickly, while a thread that already has data and keeps failing can back way off.

The empty-cache self-heal closes the loop for users who already have a poisoned cache: previously nothing ever re-fetched the full body, so the thread stayed empty forever.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Validation:

vp check # 0 errors
vp run typecheck # exit 0
vp test run packages/client-runtime/src/state/threads-sync.test.ts packages/client-runtime/src/rpc/client.test.ts # 28 passed

Plus a live pass in an isolated vp run dev environment: draft-open retry cadence measured at 250ms/500ms/1s/2s-steady (previously flat 250ms), draft submission still promotes and shows its messages, and existing threads render normally across reloads.


Note

Medium Risk
Changes thread sync and retry behavior for all thread detail subscriptions; wrong backoff or snapshot logic could delay live updates or add extra HTTP load, but scope is client-runtime only with broad test coverage.

Overview
Fixes thread views that could stay empty forever and stops draft subscriptions from hot-looping on 404s.

Empty cached thread bodies now trigger an HTTP snapshot reload before the live subscription resumes when the cached body has no messages, so replay does not skip message events below the cached sequence. If the loader returns nothing (offline/404), behavior falls back to resuming from the cached sequence as before.

Expected subscription failures use exponential backoff instead of a flat delay: retryExpectedFailureAfter accepts a duration or (attempt) => duration, consecutive failures increase the wait, and any delivered stream element resets the counter. Thread subscriptions use 250ms base doubling, capped at 2s without message data (draft / not-yet-created threads) and 10s once data exists.

Reviewed by Cursor Bugbot for commit e17b96e. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix thread subscriptions to self-heal empty thread details and back off on repeated failures

  • When a cached thread has no messages, the client now reloads the HTTP snapshot before resuming the live subscription, falling back to the cached sequence if the reload yields nothing (e.g. offline/404).
  • Replaces the fixed retryExpectedFailureAfter delay with an exponential backoff via retrySubscribeBackoff in threads.ts: capped at 2s when no data exists, 10s when cached data is present.
  • Extends SubscriptionOptions.retryExpectedFailureAfter in client.ts to accept a function (attempt: number) => Duration.Input, enabling per-attempt delay computation.
  • Backoff counter resets to zero whenever an element is delivered from the stream.
  • Behavioral Change: threads with empty cached message arrays now trigger an extra HTTP snapshot fetch before subscribing to live events.

Macroscope summarized e17b96e.

@coderabbitai

coderabbitaiBot commented Jul 23, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 75d93c73-b6dd-4ec1-a113-19f612b4dd5b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actionsgithub-actionsBot added size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jul 23, 2026

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4659cf5. Configure here.

Comment threadpackages/client-runtime/src/state/threads.ts Outdated
@macroscopeapp

macroscopeappBot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR modifies runtime retry behavior for thread subscriptions, changing from fixed 250ms delays to exponential backoff with different caps based on data state. It also adds self-healing for empty cached threads. These are meaningful behavioral changes to client synchronization that warrant human review.

You can customize Macroscope's approvability policy. Learn more.

@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch 6 times, most recently from 7c86a4a to 10b95f1CompareJuly 25, 2026 03:23
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 25, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 26, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from 10b95f1 to e3aeb3aCompareJuly 27, 2026 11:14
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 27, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from e3aeb3a to ed92adaCompareJuly 28, 2026 02:07
colonelpanic8and others added 2 commits July 27, 2026 19:46
…d subscriptions
A thread view could render empty forever in two ways:
1. When a cached thread body existed, the client always skipped the HTTP
snapshot and resumed the socket subscription from the cached snapshot
sequence. A cached body with no messages but a current sequence would
resume past the events that carried the messages and never self-heal.
The subscription now reloads the HTTP snapshot whenever the cached
body has no messages, and still degrades to the sequence resume when
the loader yields nothing.
2. Expected subscription failures retried on a flat 250ms timer forever.
An unsent composer draft subscribes to its pre-allocated thread id
before the thread exists server-side, so every open draft hot-looped
a snapshot fetch plus socket subscribe into a 404 four times a second
indefinitely. Retries now back off exponentially from 250ms, capped
at 2s while the thread has no data (so a just-submitted draft's
thread is picked up promptly) and 10s once data exists.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@colonelpanic8
colonelpanic8force-pushed the fix/thread-detail-load-hardening branch from ed92ada to e17b96eCompareJuly 28, 2026 02:51
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 10, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 10, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 11, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 18, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 28, 2026
@t3dotgg

Copy link
Copy Markdown
Member

Note

🤖 GPT-5.6 Sol responding on behalf of Theo

We're closing this PR as we clean up the T3 Code backlog. Thank you for taking the time to put this together.

This branch overlaps the subscription repairs in #8192 and #8193. Those focused PRs are the active paths for missing-thread and retry behavior.

If you believe we closed this in error, please reopen the PR and leave a comment explaining what we missed.

@t3dotggt3dotgg closed this Aug 28, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Aug 31, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Sep 2, 2026
colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Sep 2, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@colonelpanic8@t3dotgg