Skip to content

fix(extension): back off sidebar re-captures of chats that keep failing - #18

Merged
beejsbj merged 3 commits into
mainfrom
claude/asset-retry-storm-39uu6c
Oct 17, 2026
Merged

beejsbj merged 3 commits into
mainfrom
claude/asset-retry-storm-39uu6c

Conversation

@beejsbj

@beejsbj beejsbj commented Oct 17, 2026 •

Copy link
Copy Markdown
Owner

Follow-up to #17 (step 4 of the asset retry storm plan). Independent of #17: based on main, touches only the sidebar.

Problem

The sidebar reconcile loop (at most every 15 s while the sidebar DOM mutates) and the active-chat fast pass (1.5 s debounce after a mutation) record only successes in lastCapturedAt (packages/extension/src/sidebar/index.ts). A chat whose capture fails deterministically is therefore re-attempted every cycle: the provider conversation fetch, the asset pass, and the receiver POST all run again. That is what multiplied one dead asset into hundreds of reports. #17 silences the asset reports themselves; this stops the repeated conversation fetches and POSTs.

Change

  • reconcile.ts: pure helpers recordCaptureFailure, isCaptureQuarantined, captureFailureBackoffMs. A non-rate-limit failure rests the chat for 2 min, doubling per repeat failure to a 1 h cap.
  • A newer live updatedAt lifts the rest immediately, because new content may capture cleanly. A success clears it. An unknown or unparseable updatedAt is treated as 0 and still backs off.
  • index.ts: both loops consult and update one page-session map. A resting chat keeps its red error badge instead of falling back to "missing".
  • Rate-limit handling is unchanged. The state is page-session scoped, like lastCapturedAt: a reload starts fresh. The background periodic sync already has its own persisted SyncFailureTracker.

Nothing is deleted or skipped from the archive. A resting chat is simply attempted later.

Limits

  • On Gemini, listConversations never returns updatedAt (providers/gemini.ts), so new activity does not lift a rest there; only the timer does. The "newer updatedAt lifts the rest" test uses timestamps, so it covers ChatGPT and Claude behaviour.
  • The fast pass still calls listConversations each time it fires, because new activity has to be able to lift a rest. What stops is the per-chat conversation fetch, the asset pass and the receiver POST.
  • Transient failures (network, 5xx, receiver down) back off just like deterministic ones. After a long outage, a resting chat can wait up to about an hour past recovery, or reload the page.

Tests

  • test/sidebar-reconcile.test.ts: backoff growth and cap, rest and expiry, reset on newer updatedAt, unknown and NaN updatedAt.
  • test/sidebar-capture-quarantine.test.ts (new): drives runSidebarBadges with a fake DOM, timers and clock. With a chat that always fails, 5 sidebar mutation cycles make 22 captureOne calls on main. With this change they make 2, and the chat gets one more attempt once the backoff elapses. A second test confirms new activity retries immediately.
  • bun test: 970 pass, 0 fail. bun x tsc --noEmit clean.

Not verified

The change has not run in a real browser against live Gemini, ChatGPT or Claude pages. Not deployed. The extension needs a reload after merge to pick it up.


Generated by Claude Code

The sidebar reconcile loop (every 15 s while the DOM mutates) and the
active-chat fast pass (1.5 s debounce) only recorded success, so a chat
whose capture failed deterministically was re-fetched, had its asset
pass re-run, and was re-POSTed on every cycle. This was the multiplier
behind the asset retry storm.

Non-rate-limit failures now rest per chat for the page session: 2 min,
doubling to a 1 h cap. New activity (a newer live updatedAt) lifts the
rest at once; a success clears it. Rate-limit handling is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjEVUVCTsgPYaPygy2Z9yU

@beejsbj beejsbj left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Independent review: approve with nits

Reviewer: fresh-context Claude Opus 5.5 subagent at 1f81c3c (main merged in). Cross-provider reviewers were unavailable today, so this shares a model with the author.

Verified: the targeted tests pass (36) and tsc is clean. The main test is real: with main's sidebar/index.ts it fails with 22 calls instead of 2. Removing either quarantine check on its own fails it (12 calls). Rate-limit handling is untouched: rate limits never enter the backoff and still paint "missing". Both loops share the map under the running lock. Memory use is bounded.

Findings

  1. Medium-low. On Gemini, updatedAt is always null, so new activity never lifts a rest. The lift test uses timestamps, which can't happen on Gemini. A chat that fails on first load (bootstrap data unavailable) rests 2, then 4, then 8 minutes. That is not starvation: after a success, Gemini never re-captures in the same session anyway, and background sync still runs. Now stated in the PR body.
  2. Low. TransientSyncErrors (network, 5xx, receiver down) double the rest the same way deterministic failures do, after only one 500 ms retry. After an outage, recovery can lag up to about an hour. Bounded. Stated in the body; left as-is for the owner.
  3. Nit. The fast pass still calls listConversations every time it fires. Stated in the body.
  4. Nit, pre-existing. Clicking the red badge means "delete and stop syncing", and the badge is now stickier. There is no force-retry short of a reload.
  5. Nit. "Success clears the backoff" is untested. Not fixed: it can't be observed, because re-capture after a success requires a newer updatedAt, which already resets the count.

Manual browser check before marking ready

  1. ChatGPT or Claude: force a permanent failure (receiver returns 400 on /captures). In DevTools Network, check that the per-chat fetch happens once plus one retry and doesn't repeat for about 2 minutes while you scroll the sidebar. The badge should stay red. Send a message in that chat and check that a capture is attempted within seconds.
  2. Gemini: a failing chat retries at about 2, 6 and 14 minutes. A fresh tab must not park the active chat on a bootstrap error.
  3. Recovery: fix the receiver. The badge turns green on the next allowed attempt, and a reload clears the backoff.
  4. A 429 still shows "missing", not red.

@beejsbj

beejsbj commented Oct 17, 2026

Copy link
Copy Markdown
Owner Author

Live browser check (2026-10-17, Mac Chrome, real ChatGPT)

Setup: a build of this branch (with main merged) loaded unpacked. A localhost relay forwarded every request to the real receiver, except that captures of one chosen chat got a 400 (a deterministic rejection) and /status reported that chat as stale.

  • 20:43:42 and 20:43:45: the chat was captured, rejected, then retried once and rejected again. Two attempts, as expected.
  • 20:43:57: the next sidebar pass saw the chat as stale and made no attempt. On main it would have re-fetched and re-POSTed here.
  • 20:45:17 and 20:45:19, and 21:18:33: attempts from the background visit/periodic sync, which has its own SyncFailureTracker. That path is outside this PR and behaved as before.

Not observed live: the retry once the backoff ends. The window was idle, so the sidebar didn't change and no passes ran. Timing is covered by test/sidebar-capture-quarantine.test.ts.

Side note, not from this PR: a freshly installed copy starts with empty memory and re-syncs everything (about 190 chats), which caused a ChatGPT 429 that it handled.

@beejsbj
beejsbj marked this pull request as ready for review October 17, 2026 15:57
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-17T15:57:38.235959Z 1f81c3c Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@beejsbj
beejsbj merged commit e64c8c8 into main Oct 17, 2026
1 check passed
@beejsbj
beejsbj deleted the claude/asset-retry-storm-39uu6c branch October 17, 2026 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants