Skip to content

The GraphQL reserve admits a pass but does not bound it, so one fold can still spend past zero #229

Description

@Nitjsefnie

Description

The GraphQL budget reserve is an admission threshold and not a spending bound, so an admitted reconciliation can still spend past the reserve it was admitted against.

Issue #219 shipped in pull request 220: the worker reads the remaining budget, and declines to admit a new pass for a sponsor whose reading is below the reserve. The check happens once, when the job is claimed. Nothing bounds what the pass costs afterwards.

A single pass has no point ceiling, because pagination is unbounded. A fold reads every issue in the repository, and for each issue its timeline, following cursors until the connection is exhausted. A repository large enough, or one whose issues carry long timelines, can consume the reserve and continue past zero inside one admitted pass — at which point the requests fail with RATE_LIMIT mid-fold rather than the pass being held.

The reserve therefore protects against starting work that cannot finish, which is what #219 asked for, and not against a single pass exhausting the window on its own.

Expected Behavior

Either a pass stops when its sponsor's budget reaches the reserve, rather than only being prevented from starting below it, or the residual is stated somewhere an operator reads so the reserve is not mistaken for a guaranteed floor.

Reproduction Steps

Not reproduced. It follows from the shape of the change rather than from an observation: the admission check runs once per claim in the worker, and the pagination loops in src/lib/github/client.ts have no budget-aware exit. Reproducing it needs a repository large enough that one fold spans the reserve, which no registered repository currently is — the two registered repositories hold 150 and 407 issues.

Environment / Context

main at 22789bc. Disclosed in pull request 220's body by the session that implemented #219, as a known limitation of the admission model rather than a defect in it.

Related: #227 (a repository's share of its sponsor's budget) is the scheduling half of the same concern and would bound cost across passes; this issue is about cost within one pass. #196 reduces the exposure substantially, since an incremental pass paginates over what changed rather than over the whole repository — after it, a pass large enough to span the reserve means genuinely that much activity.

Discovered During

Landing #219 and #225 on 2026-09-07, and the review that followed. Raised by the implementing session as an explicit caveat: "this is an admission threshold, not a guaranteed balance."

Suggested Fix

Unverified. The reading is already refreshed on every GraphQL response, so a pagination loop could check it between pages and abandon the pass when it crosses the reserve — leaving the job to be retried rather than completing a fold with exhausted budget. The design question worth settling first is what a half-finished fold should do, because abandoning mid-pass and re-running from the start wastes what was already spent; a fold that can resume from its cursor is a different and larger change.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area: infraDeployment, database, migrations and opsbugSomething isn't workingoffered: mediumOpening catalog · comparison 5 · reserve 5settled: 6Actual catalog · 6 points

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions