Skip to content

fix(server): background PR checks spend less GitHub quota - #13189

Merged
juliusmarminge merged 2 commits into
mainfrom
audit-github-rate-limit-usage
Sep 23, 2026
Merged

juliusmarminge merged 2 commits into
mainfrom
audit-github-rate-limit-usage

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

Every T3 server re-asks GitHub about every unsettled branch once a minute (gh pr list --head, a GraphQL call). The PR lookup cache TTL was also 60s, so nearly every sweep missed it. The settlement sweep added an uncached re-check for every thread whose linked PR was merged or closed, whether or not the thread would settle. Several servers share one token, so traces showed ~2000–2900 gh calls/hour per machine and repeated GraphQL rate limiting.

Changes

  • PR lookups without an open PR are now cached for 5 minutes instead of 1. That covers "no PR yet", merged and closed. Branches with an open PR are still checked every minute. Turn end, push, create PR and a user refresh already skip this cache, so PRs opened in-app appear immediately. A PR opened outside the app shows up within 5 minutes.
  • The settlement sweep only runs its uncached branch re-check when the linked PR is merged or closed and the thread group would actually settle.

Tests

  • New tests for both behaviours in GitManager.test.ts and ThreadSettlementReactor.test.ts.
  • The retains protected worktrees … storage-cleanup tests in ThreadSettlementReactor.test.ts fail on main too. This PR does not change them.

Estimated savings

These are estimates from existing server traces (~20 min per machine). Each machine's current unsettled branches were checked against the open PRs in pingdotgg/t3code.

Model

  • Branch lookups: before, every unsettled branch cost about 1 gh pr list per minute. After, branches with an open PR still cost 1 per minute and the rest cost 1 per 5 minutes.
  • Settlement re-check: now only runs when a thread is about to settle, so it is ~0 in steady state.
  • Unchanged: PullRequestSyncReactor, status refresh and checks.
Machine Unsettled branches (open PR) Before (calls/h) After (calls/h) Cut
mbp (v2) 32 (7) ~2000 ~650 ~65%
cups 17 (3) ~2900 ~2200 ~25%
nucbox-1 1 (1) ~300 ~300 ~0%
macmini (v2) — ~60 ~60 —
Fleet (one shared token) ~5300 ~3200 ~40%

Where the savings come from

  • mbp: branch lookups fall from ~1250/h to ~470/h, because 25 of 32 branches have no open PR. The settlement re-check falls from ~430/h to ~0.
  • cups: about two thirds of its calls are PullRequestSyncReactor (one gh pr view per open linked PR per minute), which this PR does not touch.
  • nucbox-1: 90% of its calls are PullRequestSyncReactor, so this PR does nothing for it.

Caveats

  • The v2 machines need a port. mbp and macmini run the v2 services, so their share only lands once this is ported to v2. This PR alone saves the fleet ~15%.
  • The "before" numbers are low. They include time spent rate-limited and backing off, so real demand was higher. The fleet was at or above the 5000/h GraphQL budget, so ~3200/h leaves real headroom.
  • The saving grows with the share of branches that have no open PR.

Next: batch PullRequestSyncReactor into one aliased GraphQL query per repo per sweep. That would take cups from ~1900/h of gh pr view to roughly 100/h, and with the v2 port the fleet would sit under ~1500/h.

Follow-ups: port to the v2 services, and batch the per-PR gh pr view in PullRequestSyncReactor.

Done with Claude Opus 5.5 in Claude Code via T3 Code.

🤖 Generated with Claude Code


Devin Review

Summary by CodeRabbit

  • Bug Fixes
    • Open pull requests continue to refresh every minute, while merged, closed, and no-open-PR results are cached for five minutes.
    • Improved thread settlement handling for groups of threads sharing a branch. Terminal pull-request results are checked against settlement eligibility before a branch refresh, and groups that do not need settlement skip that refresh.
    • Improved handling of resumed threads with a recent user message and a linked pull request reported as merged.

Branch PR lookups without an open PR now stay cached for 5 minutes instead
of 1, so idle branches stop costing a GraphQL call on every sweep. In-app
paths that can open a PR (turn end, push, create PR, user refresh) still
bypass the cache.

The settlement sweep only re-checks a branch uncached when the linked PR is
terminal and the group would actually settle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@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 Sep 23, 2026
@github-actions

github-actions Bot commented Sep 23, 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 13.5 KiB 13.5 KiB +27 B (+0.2%) 15.1 KiB ✅
Codex Thread snapshot wire 7.1 KiB 7.1 KiB +4 B (+0.1%) 7.3 KiB ✅
Codex Live turn WebSocket wire 6.5 KiB 6.5 KiB +23 B (+0.3%) 7.8 KiB ✅
Codex Live turn WebSocket decoded 56.3 KiB 56.3 KiB 0 B (0.0%) 66.4 KiB ✅
Codex Live turn messages 10 10 0 (0.0%) 21 ✅
Claude Total thread wire 13.5 KiB 13.5 KiB −8 B (−0.1%) 15.1 KiB ✅
Claude Thread snapshot wire 7.1 KiB 7.1 KiB +13 B (+0.2%) 7.3 KiB ✅
Claude Live turn WebSocket wire 6.5 KiB 6.5 KiB −21 B (−0.3%) 7.8 KiB ✅
Claude Live turn WebSocket decoded 57.1 KiB 57.0 KiB −44 B (−0.1%) 66.4 KiB ✅
Claude Live turn messages 10 9 −1 (−10.0%) 21 ✅

Baseline: 6975efd · PR result: 5294f96 · 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: 114.0 KiB
  • Claude decoded thread snapshot: 114.7 KiB

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

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Sep 23, 2026
@macroscopeapp

macroscopeapp Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 5294f96

Macroscope's review found this PR approvable — This is a focused server-side optimization that reduces background GitHub requests with a bounded five-minute cache for non-open results and preserves explicit refresh behavior. The settlement guard and cache semantics are covered by targeted tests, with no schema, security, billing, or deployment impact.

You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

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

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

⚙️ Run configuration

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

Review profile: CHILL

Plan: Team

Run ID: 1fb1b20a-40b4-4e74-b91f-cdfd1acb0971

📥 Commits

Reviewing files that changed from the base of the PR and between 5294f96 and 5294f96.

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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Team

Run ID: 6ad64fbc-76e8-4fdb-b6f9-901b453340a3

📥 Commits

Reviewing files that changed from the base of the PR and between 901ebf2 and 5294f96.

📒 Files selected for processing (3)
  • apps/server/src/git/GitManager.test.ts
  • apps/server/src/orchestration/ThreadSettlementReactor.test.ts
  • apps/server/src/orchestration/ThreadSettlementReactor.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 5 remain after this review.


📝 Walkthrough

Walkthrough

Branch pull-request caching now uses different durations for open and terminal or absent results. Thread settlement groups check whether settlement can occur before refreshing branch data and reuse normalized results. Tests cover cache expiry and settlement behavior for a resumed thread.

Changes

Pull-request lookup and settlement

Layer / File(s) Summary
Outcome-based pull-request cache policy
apps/server/src/git/GitManager.ts, apps/server/src/git/GitManager.test.ts
Open pull requests use a 60-second cache. Merged, closed, and absent results use a five-minute cache. Clock-controlled tests verify lookup behavior at each interval.
Grouped settlement lookup
apps/server/src/orchestration/ThreadSettlementReactor.ts, apps/server/src/orchestration/ThreadSettlementReactor.test.ts
Settlement groups check whether any member would settle before refreshing branch data. The code normalizes terminal results once and reuses pull-request data across group members. A test verifies that a resumed thread with a merged linked-PR summary performs no branch lookup or settlement dispatch.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Suggested reviewers: maria-rcks

Merge Risk: ⚪ Minimal · up to 5294f

No actionable merge-blocking issue is established. Normal checks should confirm the cache timing and settlement tests before merging.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: reducing GitHub quota use from background PR checks.
Description check ✅ Passed The description explains what changed and why, and includes tests, impact estimates, caveats, and follow-ups. The template’s checklist section is omitted, but the description is otherwise complete; UI…
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 4…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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

…anch

A group that only became eligible after the branch recheck was skipped
could settle on a stale terminal link. It now waits for the next sweep.
Tests pin the zero-lookup case and both sides of the 5-minute TTL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@macroscopeapp
macroscopeapp Bot dismissed their stale review September 23, 2026 02:43

Dismissing prior approval to re-evaluate 5294f96

@juliusmarminge
juliusmarminge added this pull request to stack #13199 September 23, 2026 03:13
@juliusmarminge
juliusmarminge merged commit 5975ec7 into main Sep 23, 2026
24 checks passed
@juliusmarminge
juliusmarminge deleted the audit-github-rate-limit-usage branch September 23, 2026 03:50
aorwall added a commit to aorwall/t3code that referenced this pull request Sep 23, 2026
Merges `pingdotgg/t3code` at `aca3c87cd` into the fork — 62 commits from
base
`5a61f50cc`. Landed 339 files (8865+/6131-) against 335 in the upstream
range;
fork delta unchanged at 786 files. The gap of 4 is the three `docs/fork`
files
below plus `ThreadStatusIndicators.test.tsx`, which auto-merged clean
while
still passing the `variant` prop upstream deleted.

`verify.mjs`: all 10 checks pass, tests included.

## Conflicts

Six, each resolved with the verdict `preflight.mjs` printed.

| Path | Verdict | Resolution |
| --- | --- | --- |
| `DiffPanel.tsx` | converged — diff-panel-gates | Upstream rewrote the
scope dropdown as `DropdownMenuRadioGroup` / `DropdownMenuRadioItem`
with the per-turn list in a submenu. Took it whole; re-applied the two
`FEATURES.turnDiffs` gates at their new anchors. |
| `LegacySidebar.tsx` | converged — mobile-touch-upstream-files |
Upstream deleted the add-project button's icon-color className. Took
upstream's button inside the fork's `FEATURES.projectManagement`
wrapper. |
| `ThreadStatusIndicators.tsx` | converged — thread-status-indicators |
Upstream swapped `variant` for a `render` prop and extracted
`PullRequestBadge`. The fork's several-links popover is now a sibling
`PullRequestLinksBadge`; both render a shared `PullRequestBadgeFace`,
extracted so `duplicate-adds.mjs` does not read the shared icon-and-text
span as a merge artifact. |
| `settings/ProjectActionsList.tsx` | unlisted → decide-then-add-entry |
pingdotgg#13029 restyled the Edit button to `variant="ghost-muted"`. Took
upstream's button, kept the `editable` wrapper, added a
`project-actions-list` inventory entry. |
| `settings/ProjectDefaultsSettings.tsx` | converged —
project-defaults-settings | Took upstream whole, re-applied the five
`workspaceOwnsProjectDefaults` gates. Upstream now renders the model and
workspace rows from both a `category === "project"` and a `category ===
"general"` branch, so each gate exists twice. |
| `settings/ProjectSettingsPanel.tsx` | converged —
project-settings-panel | Upstream's new info Alert is unconditional and
first; its new `<ProjectDefaultsSettings category="project" />` section
is gated on `workspaceSettings` as a sibling rather than a fragment,
because folding it in would have re-indented upstream's JSX. |

`pnpm-lock.yaml` auto-merged and was re-derived with `install.mjs`,
which moved
`type-fest` 5.7.0 → 5.10.0 in two msw snapshot blocks and nothing else.
The
fork's `moatless-api` and `mermaid` edges survive.

Sweep: one hit — `apps/server/src/provider/ProviderAuthFlow.ts` and its
test,
new from pingdotgg#12983 on the keyword `auth`. A false positive for the
auth-session
concern (a provider CLI's own sign-in, not the user session), taken
as-is, and
recorded under _Provider setup_ in the gaps because it adds
`provider.auth.respond`. Tripwires steady; no new upstream workflows; no
stale
inventory entries.

Unsupported methods: ADD and DROP both empty, KEEP unchanged at two. No
`rpc.ts` union edits — `provider.auth.respond` took
`ProviderSetupRpcError`,
which already carries `UnsupportedMethodError`.

## Feature classification

### Usable as-is

- **The web component-library pass** — roughly 24 commits
(pingdotgg#12984–pingdotgg#13043) over
`apps/web/src/components/ui/*`: button variants and sizes, dropdown
radio
groups and submenus, popover and tooltip `render` props. Nothing here
touches
the wire; it is the source of four of the six conflicts and all of them
were
  restyles.
- **Settings scope sentence and scope pickers in breadcrumbs** —
`242816af8`
  and `db9a0671b`, including the new `SettingsScopeSentence.tsx`. Reads
  settings the fork already serves.
- **Small web fixes** — `219c1d265`, `6975efd3d`, `68607c5a9`,
`b954af60c`,
  `7c2702d68` + `da6a85b13`, `aff9318bf`, `438bf466f`.
- **Every mobile-only commit**, and the non-targets: `e4422eec7`
(desktop),
  `d7819c188` (device-hub bump, moot while `FEATURES.deviceHub` is off),
  `ca864a25b` / `f25a8e4b7` / `17e34773b` (model manifest).

### Unsupported in Moatless / needs implementation

- **`7e65b226e` feat(auth): share provider sign-in flows and credential
  bindings (pingdotgg#12983).** Adds `provider.auth.respond`
(`WS_METHODS.providerAuthRespond`) and `ProviderAuthRespondInput`,
reshapes
  `provider.auth.start`'s payload from `ProviderSetupInput` to
  `ProviderAuthStartInput`, and adds `methods`, `interaction` and
`credentialOwner` to `ProviderAuthState`. Server-only implementation in
  `apps/server/src/provider/ProviderAuthFlow.ts` and
  `ProviderCredentialStore.ts`.

It makes a provider sign-in interactive — the server asks, the client
answers
through the new method — which is the same shape as the nine
`provider.auth.*`
/ `provider.install.*` methods the backend already refuses. It falls
under the
existing _Provider setup_ gap, now listing ten methods; it needed no
union
edit because it took the same error type as its siblings. Closes if
Moatless
  ever manages provider credentials on the client's behalf.

### Backend behavior to consider reproducing in Moatless

All six are now bullets under _Runtime fixes upstream made to its own
server_
in `docs/fork/gaps.md`, with the closing condition on each. Four are
GitHub
quota work.

- **`96c4bfa0a` provider compatibility advisory** (pingdotgg#13130,
`apps/server/src/provider/providerCompatibility.ts`) — a per-driver
policy on
the model manifest yields a `supported` / `unsupported` / `broken`
advisory on
the published `ServerProvider`, rendered above the composer. Moatless
installs
  the provider CLIs, so it is the side that knows the version.
- **`f193a6863` bypass owned caches on an explicit provider refresh**
(pingdotgg#13109) —
splits `server.refreshProviders` by its existing `refreshModels` flag: a
user
refresh force-refreshes the model manifest and version cache, background
polls
  keep their timers. The flag is already on the contract.
- **`eafb4a934` GitHub PR lookups stop probing owner-qualified heads**
(pingdotgg#13200)
— `gh pr list --head` answers an `owner:branch` selector with nothing
while
still spending a GraphQL call; upstream drops those selectors and widens
the
  remaining probe to 100, which GitHub prices like `first:1`.
- **`18de6bb32` batched PR summary reads** (pingdotgg#13198) — summaries batch
behind a
10ms request window and a per-batch GraphQL query, with the sync
reactor's
  concurrency raised 8 → 25 so a sweep's reads land in the same window.
- **`5975ec78b` `wouldSettle` before an uncached PR re-query** (pingdotgg#13189)
— a
settlement sweep pays for the reused-branch-race lookup only when some
thread
  in the group would actually settle.
- **`f22331240` three-dot PR diffs** (pingdotgg#13170) — `readRangeContext` moved
its
diff stat and patch to `base...HEAD` while leaving the commit log
two-dot, so
a PR description written after the base advanced describes the branch's
own
  changes. One character per command.

## Docs

- `docs/fork/inventory.json` — new `project-actions-list` entry;
  `provider-settings-gates` gained `ProviderModelsSection.tsx` and
  `ProviderSettingsPanel.environment.test.tsx`.
- `docs/fork/gaps.md` — _Provider setup_ names `provider.auth.respond`
as its
tenth method; six new bullets under _Runtime fixes upstream made to its
own
  server_.
- `docs/fork/upstream-merge-log.md` — dated entry.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---
Moatless task:
https://moatless.soaplabstest.com/tasks/5258e642-4bee-4f38-878b-747496b2d2ea
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 23, 2026
## What's Changed
* chore(mobile): drop dead nitro-markdown tgz override and @expo/metro-runtime by @juliusmarminge in pingdotgg/t3code#13148
* feat(web): show settings scope as a sentence at the top of the page by @juliusmarminge in pingdotgg/t3code#13139
* refactor(web): move settings scope pickers into breadcrumbs by @Yash-Singh1 in pingdotgg/t3code#13165
* feat(auth): share provider sign-in flows and credential bindings by @juliusmarminge in pingdotgg/t3code#12983
* refactor(mobile): git sheets use uniwind platform variants instead of className ternaries by @juliusmarminge in pingdotgg/t3code#13161
* chore(mobile): name the two project favicon caches by their job by @juliusmarminge in pingdotgg/t3code#13160
* revert(mobile): git sheets back to Platform.OS ternaries (un-guarded uniwind variants broke both platforms) by @juliusmarminge in pingdotgg/t3code#13169
* docs(mobile): document the two mobile routes that intentionally skip deep links by @juliusmarminge in pingdotgg/t3code#13164
* refactor(mobile): break module cycles with focused extractions by @juliusmarminge in pingdotgg/t3code#13151
* fix(server): generate PR diffs from branch changes by @Yash-Singh1 in pingdotgg/t3code#13170
* fix(web): preserve nested scroll behavior in chat timeline by @Yash-Singh1 in pingdotgg/t3code#13167
* test(web): cover usage model ordering without static markup by @flamboh in pingdotgg/t3code#13104
* fix(desktop): find linuxbrew node for the WSL backend by @CodyRay in pingdotgg/t3code#7827
* chore(models): use GPT-6 Luna for text generation by @extoci in pingdotgg/t3code#13115
* fix(mobile): keep ordinary offline outbox failures out of console.warn by @juliusmarminge in pingdotgg/t3code#13144
* feat(providers): check remote compatibility ranges by @juliusmarminge in pingdotgg/t3code#13130
* chore(lint): keep mobile theme escape-hatch allowlist honest by @juliusmarminge in pingdotgg/t3code#13146
* fix(web): the pull request badge reads at the meta size again by @juliusmarminge in pingdotgg/t3code#13175
* fix(mobile): uniwind platform variants stay guarded on both platforms by @juliusmarminge in pingdotgg/t3code#13172
* refactor(mobile): git sheets use uniwind platform variants instead of className ternaries by @juliusmarminge in pingdotgg/t3code#13185
* refactor(mobile): remaining className platform ternaries become class variants by @juliusmarminge in pingdotgg/t3code#13188
* fix(web): align provider emails without clipping by @Derpedyea in pingdotgg/t3code#13174
* perf(mobile): recycle the default v2 home list and scope the snooze minute tick by @juliusmarminge in pingdotgg/t3code#13149
* refactor(mobile): retire the legacy grouped thread list by @juliusmarminge in pingdotgg/t3code#13183
* fix(server): background PR checks spend less GitHub quota by @juliusmarminge in pingdotgg/t3code#13189
* fix(server): background PR sync reads summaries in batches by @juliusmarminge in pingdotgg/t3code#13198
* fix(server): GitHub PR lookups stop probing owner-qualified heads by @juliusmarminge in pingdotgg/t3code#13200
* chore(mobile): clear the legacy-list deletion fallout by @juliusmarminge in pingdotgg/t3code#13203

## New Contributors
* @CodyRay made their first contribution in pingdotgg/t3code#7827

**Full Changelog**: pingdotgg/t3code@v0.0.43-nightly.20260922.2123...v0.0.43-nightly.20260923.2135

Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.43-nightly.20260923.2135
jvelascodev pushed a commit to jvelascodev/t3code that referenced this pull request Sep 24, 2026
…13189)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.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