Description
Reconciliation gives every repository the same claim on a sponsor's GitHub budget regardless of what it costs to fold, so one expensive repository can spend a sponsor's whole window and leave their other repositories stale.
The queue has nothing to schedule on. repository_reconciliation_jobs carries run_after, attempt_count, lease columns and follow_up_requested, and no measure of what a fold cost or is expected to cost. Claiming is therefore due-time ordering: whichever job is due next is taken, and a repository that costs ten times another's budget share is indistinguishable at the point of the decision.
The disparity is already large at two repositories, both belonging to one sponsor:
| repository |
issues |
fold avg |
fold max |
Nitjsefnie/Overflow |
150 |
48.7 s |
61.3 s |
Nitjsefnie-Harness-Commons/daedalus |
407 |
165.3 s |
180.1 s |
Fold time tracks issue count closely, so daedalus consumes roughly three times Overflow's share of one shared 5,000/hour GraphQL budget, and the scheduler has no way to know that or act on it. When that budget ran out on 2026-09-07 at 09:28 — GraphQL used: 5000, remaining: 0 while core REST sat at 4,973 — both repositories stopped together, including the cheap one whose own activity had not caused it.
Expected Behavior
A repository's share of its sponsor's budget is bounded by something, so an expensive or highly active repository degrades its own freshness rather than everyone's. A sponsor with one large repository and several small ones should see the large one fall behind under pressure while the small ones stay current, instead of all of them stopping at the same moment.
Reproduction Steps
- Register two repositories of very different sizes under one sponsor.
- Let reconciliation run until that sponsor's GraphQL budget is exhausted.
- Observe both repositories stop. Nothing in
repository_reconciliation_jobs or the claim query distinguishes the repository that consumed the budget from the one that did not.
Environment / Context
main at 1faa4fe. Depends on work already in flight and should not be designed before it lands:
That last point is the reason not to build this yet, and the reason to be careful about the word "deprioritise" when it is built: after #196 an expensive repository is a busy one, and busy is exactly the case where staleness is most visible to a member. The recorded target for acceptable staleness is 10 to 30 seconds. A policy that simply demotes the heaviest consumer optimises the wrong thing — it makes the active repository, whose users are watching, the one that falls behind.
Discovered During
A scalability review on 2026-09-07 prompted by the maintainer, after the GraphQL exhaustion above. Raised by the maintainer as "a repo that generates too much traffic should be deprioritised".
Suggested Fix
Unverified, and the shape matters more than the mechanism.
Charge each fold what it actually cost, using the rateLimit.cost figure #219 makes readable, and keep a decaying per-repository total. Then bound a repository's share of its sponsor's remaining window rather than ranking repositories against each other: a repository over its share has its next claim deferred through the existing deferReconciliationJob path, which already releases the lease and is how a cooled-down repository is handled today.
Bounding a share degrades the heavy repository's freshness in proportion to its own consumption while leaving others untouched. Ranking, by contrast, starves whichever repository is last — and under sustained pressure that is the same repository every cycle, which is the failure this issue is about, merely relocated.
Description
Reconciliation gives every repository the same claim on a sponsor's GitHub budget regardless of what it costs to fold, so one expensive repository can spend a sponsor's whole window and leave their other repositories stale.
The queue has nothing to schedule on.
repository_reconciliation_jobscarriesrun_after,attempt_count, lease columns andfollow_up_requested, and no measure of what a fold cost or is expected to cost. Claiming is therefore due-time ordering: whichever job is due next is taken, and a repository that costs ten times another's budget share is indistinguishable at the point of the decision.The disparity is already large at two repositories, both belonging to one sponsor:
Nitjsefnie/OverflowNitjsefnie-Harness-Commons/daedalusFold time tracks issue count closely, so daedalus consumes roughly three times Overflow's share of one shared 5,000/hour GraphQL budget, and the scheduler has no way to know that or act on it. When that budget ran out on 2026-09-07 at 09:28 — GraphQL
used: 5000, remaining: 0while core REST sat at 4,973 — both repositories stopped together, including the cheap one whose own activity had not caused it.Expected Behavior
A repository's share of its sponsor's budget is bounded by something, so an expensive or highly active repository degrades its own freshness rather than everyone's. A sponsor with one large repository and several small ones should see the large one fall behind under pressure while the small ones stay current, instead of all of them stopping at the same moment.
Reproduction Steps
repository_reconciliation_jobsor the claim query distinguishes the repository that consumed the budget from the one that did not.Environment / Context
mainat1faa4fe. Depends on work already in flight and should not be designed before it lands:DEBUG_GITHUB_COSTlog line. The same GraphQL response carriesrateLimit { cost remaining }, so the per-fold cost this issue needs is already on the wire and merely discarded — that is what makes charging a repository for what it actually spent cheap rather than speculative.That last point is the reason not to build this yet, and the reason to be careful about the word "deprioritise" when it is built: after #196 an expensive repository is a busy one, and busy is exactly the case where staleness is most visible to a member. The recorded target for acceptable staleness is 10 to 30 seconds. A policy that simply demotes the heaviest consumer optimises the wrong thing — it makes the active repository, whose users are watching, the one that falls behind.
Discovered During
A scalability review on 2026-09-07 prompted by the maintainer, after the GraphQL exhaustion above. Raised by the maintainer as "a repo that generates too much traffic should be deprioritised".
Suggested Fix
Unverified, and the shape matters more than the mechanism.
Charge each fold what it actually cost, using the
rateLimit.costfigure #219 makes readable, and keep a decaying per-repository total. Then bound a repository's share of its sponsor's remaining window rather than ranking repositories against each other: a repository over its share has its next claim deferred through the existingdeferReconciliationJobpath, which already releases the lease and is how a cooled-down repository is handled today.Bounding a share degrades the heavy repository's freshness in proportion to its own consumption while leaving others untouched. Ranking, by contrast, starves whichever repository is last — and under sustained pressure that is the same repository every cycle, which is the failure this issue is about, merely relocated.