Skip to content

fix(server): stop PR status lookups amplifying GitHub rate limits - #5673

Merged
t3dotgg merged 1 commit into
mainfrom
t3code/investigate-github-api-throttling
Aug 8, 2026
Merged

fix(server): stop PR status lookups amplifying GitHub rate limits#5673
t3dotgg merged 1 commit into
mainfrom
t3code/investigate-github-api-throttling

Conversation

@t3dotgg

@t3dotgg t3dotgg commented Aug 8, 2026

Copy link
Copy Markdown
Member

Problem

I keep getting throttled on the GitHub API. Digging through logs, most of it is
my own agents running gh in bursts, but T3 Code makes it worse in two ways.

The PR status badge caches a successful lookup for 2 minutes and a failed
one for 20 seconds. GitHub rejects a throttled request instantly, so once you're
rate limited the poller starts asking six times faster than it does when
everything is healthy. On top of that, lookupStatusPr catches the failure and
returns the last known PR, so the poll reports success and
VcsStatusBroadcaster's exponential backoff never engages. Nothing ever slows
down, and a transient 429 turns into a sustained one.

Separately, the badge asks the provider about every branch, including branches
that were never pushed anywhere. Those queries cannot match a PR. On this machine
66% of sampled t3code worktrees are in that state, so two thirds of the baseline
API spend was guaranteed-empty.

Solution

Failed lookups now back off per branch (20s, 40s, 80s, ... capped at 15 min)
instead of retrying on a flat 20s timer, so a failing branch drops below the
healthy refresh rate after a couple of attempts rather than above it.

Branches git has no record of on any remote skip the provider call entirely. The
check looks for a remote-tracking ref rather than upstream config, because
git push writes that ref even without -u, which is how most terminal and
agent pushes land. Repositories that track no remotes at all can't answer the
question, so they keep the old behaviour, as does any failed probe: hiding a PR
badge is worse than spending the lookup.

Tests cover the backoff curve, the skip, and the case that makes the naive
version wrong (a branch pushed without --set-upstream still gets looked up).

Not in this PR

Two bigger wins from the same investigation, kept separate because they change
lookup semantics and deserve their own review:

  • Batch the sidebar into one gh pr list per repo matched locally by branch,
    instead of one call per branch. Caps the worst case (expanded thread list,
    262 distinct cwds here) at a few calls instead of thousands.
  • Stop worktree dev servers from polling GitHub at all. Four servers were live
    during the investigation, each with independent pollers and caches against the
    same token.

Made by Claude Opus 5 (1M context) in Claude Code, on T3 Code.


Note

Medium Risk
Changes PR badge lookup timing and when GitHub is contacted; incorrect unpublished detection could hide a PR badge, though failures fall back to the old lookup path.

Overview
Reduces GitHub API load from VCS PR status polling by changing how failed lookups are cached and when provider calls run.

Failed PR lookups no longer use a flat 20s cache TTL. They use per-branch exponential backoff (20s → 40s → … capped at 15 minutes), with streak tracking cleared on success, so rate-limited branches retry less often than the healthy 2-minute refresh instead of more often.

For branches without upstream config, status can skip gh pr list when git shows remotes exist but no refs/remotes/*/<branch> for the current head—treating never-pushed local branches as having no PR. Branches that were pushed without -u still get looked up because remote-tracking refs exist. Ambiguous cases (no remotes, or probe failure) still call the provider.

Tests cover the backoff curve, the never-pushed skip, and push-without-upstream still querying GitHub.

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

Note

Fix PR status lookups amplifying GitHub rate limits via exponential backoff and branch skip logic

  • Repeated PR lookup failures now back off exponentially per branch (base 20s, max 15min) using a new prLookupFailureTtl(consecutiveFailures) function, resetting on success.
  • Branches that have never been pushed to any remote skip the provider PR lookup entirely, using a new isUnpublishedBranch check via git for-each-ref.
  • Branches pushed without --set-upstream still perform the provider lookup as before.
  • Behavioral Change: branches with no remote-tracking ref and no push history return no PR without making any GitHub API calls.

Macroscope summarized a76cbc6.

A failed PR lookup was cached for a flat 20s while a successful one is
cached for 2 minutes, so a rate-limited poller re-asked six times faster
than a healthy one. lookupStatusPr also catches the failure and returns
the last known PR, so the poll succeeds and VcsStatusBroadcaster's
exponential backoff never engages. A transient 429 therefore became
sustained pressure.

Back the failure TTL off per branch instead, and skip the provider call
entirely for branches git has never seen on any remote, which cannot
have a change request.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 8, 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: 74956aaf-5cb4-41d9-8824-56b90fc108fc

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

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-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Aug 8, 2026
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 11.3 KiB 11.3 KiB +23 B (+0.2%) 15.1 KiB
Codex Thread snapshot wire 5.5 KiB 5.5 KiB +3 B (+0.1%) 7.3 KiB
Codex Live turn WebSocket wire 5.9 KiB 5.9 KiB +20 B (+0.3%) 7.8 KiB
Codex Live turn WebSocket decoded 49.7 KiB 49.7 KiB 0 B (0.0%) 66.4 KiB
Codex Live turn messages 16 16 0 (0.0%) 21
Claude Total thread wire 11.3 KiB 11.3 KiB −19 B (−0.2%) 15.1 KiB
Claude Thread snapshot wire 5.5 KiB 5.5 KiB 0 B (0.0%) 7.3 KiB
Claude Live turn WebSocket wire 5.9 KiB 5.8 KiB −19 B (−0.3%) 7.8 KiB
Claude Live turn WebSocket decoded 50.6 KiB 50.6 KiB 0 B (0.0%) 66.4 KiB
Claude Live turn messages 16 16 0 (0.0%) 21

Baseline: 31891a1 · PR result: a76cbc6 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 94.6 KiB
  • Claude decoded thread snapshot: 95.4 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

macroscopeapp Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved a76cbc6

Self-contained performance optimization that adds exponential backoff for failed PR lookups and skips unnecessary API calls for branches never pushed to a remote. Well-tested with clear, conservative logic.

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

@t3dotgg
t3dotgg merged commit 4eaf5ef into main Aug 8, 2026
17 checks passed
@t3dotgg
t3dotgg deleted the t3code/investigate-github-api-throttling branch August 8, 2026 01:09
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Aug 8, 2026
## What's Changed
* feat: sidebar v2 is now the default sidebar by @t3dotgg in pingdotgg/t3code#5672
* fix(server): stop PR status lookups amplifying GitHub rate limits by @t3dotgg in pingdotgg/t3code#5673
* fix(web): delay transient reconnect warnings by @t3-code[bot] in pingdotgg/t3code#5670
* fix(web): inherit terminal size in simple typography by @chrisdeeming in pingdotgg/t3code#5628


**Full Changelog**: pingdotgg/t3code@v0.0.33-nightly.20260808.1029...v0.0.33-nightly.20260808.1030

Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.33-nightly.20260808.1030
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Aug 8, 2026
…ngdotgg#5673)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Aug 8, 2026
…ngdotgg#5673)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Aug 8, 2026
…ngdotgg#5673)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Aug 8, 2026
…ngdotgg#5673)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Aug 8, 2026
…ngdotgg#5673)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
cursor Bot pushed a commit to aaditagrawal/t3code that referenced this pull request Aug 8, 2026
…ngdotgg#5673)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
omegent-app Bot added a commit to patroza/t3code that referenced this pull request Aug 8, 2026
Adopts the thirteen upstream commits after #379. The headline is pingdotgg#5672 making
sidebar v2 the default, which is a file-level restructure: upstream renamed the
classic sidebar to LegacySidebar.tsx and moved v2's content into Sidebar.tsx,
deleting SidebarV2.tsx.

Git could not follow either rename because both sides had changed both files,
so each was merged three-way by hand:

- Sidebar.tsx = the fork's SidebarV2.tsx (multi-env and ownership filters,
  identity marks, budgeted listStatus, pin ordering, copy-thread-id, unread
  and regenerating-title chrome) with upstream's pingdotgg#5672 delta applied. That
  delta is a rename sweep — SidebarV2* → Sidebar*, group/v2-row →
  group/sidebar-row, sidebar-v2-* test ids — plus aria-busy on the rows.
- LegacySidebar.tsx = the fork's old Sidebar.tsx with upstream's rename and
  settings-nav hoist applied. AppSidebarLayout now renders SettingsSidebarNav
  for both sidebars, so the legacy one no longer renders it itself.

forkSurfaceExistence, the fork's anti-stack-drop net, was retargeted at the
renamed files and ids rather than relaxed: all 17 assertions still run, and
they are what caught LegacySidebar initially being upstream's v1 rather than
the fork's.

Other resolutions:
- GitManager: upstream's per-branch exponential backoff for rate-limited PR
  lookups (pingdotgg#5673) with the fork's terminal-state freeze helper kept.
- Composer: pingdotgg#5554 shows Stop while input is pending, which is exactly what the
  fork's shouldShowComposerInterruptAction suppressed; the predicate and its
  tests are retired rather than merged.
- PreviewView: the fork's resolveNavigableUrl (asks the environment about
  tailnet routing) keeps resolving, with upstream's browser-history recording
  (pingdotgg#5270) layered on the successful-navigation path.
- Settings: sidebarV2Enabled / sidebarV2ConfiguredByUser and
  enableAssistantStreaming are retired in favour of upstream's fresh keys
  (legacySidebarEnabled, enableLegacyTokenStreaming), which deliberately reset
  prior opt-ins; the mobile list toggle inverts the same way, so HomeScreen now
  reads through resolveThreadListV2Enabled.
- .github/VOUCHED.td stays deleted: it is upstream's contributor-vouching file.

Adversarial review caught one surface the key migration missed: the mobile
ThreadNavigationSidebar still gated v2 on the retired threadListV2Enabled, so
the iPad/split list would have stayed on the legacy layout with no setting able
to change it, disagreeing with Home. It now reads through
resolveThreadListV2Enabled like HomeScreen.

Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant