Skip to content

Separate rate-budget accounting for App-JWT and installation-token requests #25

Description

@ewhauser

Problem

The budget Gate maintains a single REST ResourceBudget (internal/budget/gate.go), and every rate-limit observation merges into it. App-JWT traffic (webhook-delivery APIs) and installation-token traffic (repository APIs) are different authentication contexts with separate GitHub rate-limit pools (App JWT ~5,000/hour for the app; GHEC installation tokens 15,000/hour), but their x-ratelimit-* headers currently overwrite each other's shared admission state.

During the 2026-08-04 rate-limit incident, ghsync reported remaining budget and kept admission open while GitHub returned "rate limit exceeded for installation ID" — consistent with a JWT response's headers masking the exhausted installation pool.

Proposed behavior

  • Key ResourceBudget state by credential context (installation REST, App-JWT REST; GraphQL is already separate).
  • Per-context admission, floor reservations, backoff, and lease-persisted snapshots; a header observation updates only its own context.
  • Metrics (ghsync_c_b3_*) gain an auth-context label so exhaustion is attributable.

Acceptance criteria

  • A 403/backoff in one context does not block or misreport admission in the other.
  • Observed limits per context match GitHub's actual pools; the merged-header masking failure mode has a regression test.
  • Budget lease snapshot/restore round-trips both contexts.
  • Conformance/loadgen coverage exercises both contexts concurrently.

Refs: GitHub App installation rate limits

Observed on v0.3.3.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions