Repository navigation
fix(extension): back off sidebar re-captures of chats that keep failing - #18
Conversation
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
left a comment
There was a problem hiding this comment.
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
- Medium-low. On Gemini,
updatedAtis 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. - 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. - Nit. The fast pass still calls
listConversationsevery time it fires. Stated in the body. - 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.
- 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
- 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. - 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.
- Recovery: fix the receiver. The badge turns green on the next allowed attempt, and a reload clears the backoff.
- A 429 still shows "missing", not red.
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
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 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. |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
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 helpersrecordCaptureFailure,isCaptureQuarantined,captureFailureBackoffMs. A non-rate-limit failure rests the chat for 2 min, doubling per repeat failure to a 1 h cap.updatedAtlifts the rest immediately, because new content may capture cleanly. A success clears it. An unknown or unparseableupdatedAtis treated as 0 and still backs off.index.ts: both loops consult and update one page-session map. A resting chat keeps its rederrorbadge instead of falling back to "missing".lastCapturedAt: a reload starts fresh. The background periodic sync already has its own persistedSyncFailureTracker.Nothing is deleted or skipped from the archive. A resting chat is simply attempted later.
Limits
listConversationsnever returnsupdatedAt(providers/gemini.ts), so new activity does not lift a rest there; only the timer does. The "newerupdatedAtlifts the rest" test uses timestamps, so it covers ChatGPT and Claude behaviour.listConversationseach 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.Tests
test/sidebar-reconcile.test.ts: backoff growth and cap, rest and expiry, reset on newerupdatedAt, unknown and NaNupdatedAt.test/sidebar-capture-quarantine.test.ts(new): drivesrunSidebarBadgeswith a fake DOM, timers and clock. With a chat that always fails, 5 sidebar mutation cycles make 22captureOnecalls onmain. 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 --noEmitclean.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