Skip to content

fix: cut ci-status API polling and make the intake labeler wait out a rate limit #855

Open

Description

@kyle-sexton

Problem

The claude-code-plugins repository spends its whole GITHUB_TOKEN core bucket in under an hour. Run 39732240888's headers read remaining=0 used=5000 limit=5000, with the reset at 06:16:20Z. Lanes then fail at about XX:09-11, just before each reset.

Measured in melodic-software/claude-code-plugins#9048 (2026-10-22, runs 04:14-06:14Z), two consumers in ci-workflows stand out:

  • pr-require-checks aggregate-results (ci-status): about 77 runs/hr. A static estimate puts it at about 15-30 calls per run, roughly 1,150-2,300/hr, likely the top consumer. Its waits poll the sibling runs every 10 s during a 90 s rerun wait, and every 5 s during a 30 s yield.
  • intake-label-needs-triage: its github-script step fails at once on a rate-limit 403. Run 39732372419 failed this way, so a new issue never got its floor needs-triage label.

Proposal

  1. In intake-label-needs-triage.yml's github-script step, on a 403/429, wait once the way gh-api.sh does: retry-after if set, else the reset time when remaining is 0, else 60 s. Cap the wait inside the job timeout, then fail with the reset time in the message.
  2. Add an api-calls count line to gh-api.sh, the way claude-code-plugins' budgetedFetch prints api-calls rest=N, so each run reports what it spent.
  3. Using those numbers, cut aggregate-results' polling: list the sibling runs once per wait step, or back off between polls instead of polling at a fixed 10 s/5 s.

Acceptance

  • A rate-limited intake run waits and applies the label once the bucket resets.
  • ci-status runs report their API call count.
  • After the polling change, per-run calls drop by a measured amount.

Activity

  1. added
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Oct 22, 2026
  2. kyle-sexton commented on Oct 25, 2026

    @kyle-sexton
    MemberAuthor

    One more intake cut belongs here, from the claude-code-plugins GITHUB_TOKEN budget work on 2026-10-25.

    The repository owner set the plan for GITHUB_TOKEN at GitHub's documented 1,000 requests/h per repository (REST rate limits: "The rate limit for GITHUB_TOKEN is 1,000 requests per hour per repository."). Measured core use in claude-code-plugins is about 4,470/h across the two hourly counters seen in its headers. On 10-24 one counter was refused at used=5000 (run 40113473063).

    Intake is among the top spenders and cannot be cut from the caller. claude-code-plugins calls intake-triage.yml@215cec5 (v0.65.10, intake-triage.yml:204 there) and intake-label-needs-triage.yml@5e18f3d (v0.58.0, intake-label-needs-triage.yml:108). The estimate for intake triage is 430-600 core calls/h at about 17 runs/h. That figure is an estimate (UNVERIFIED): these reusables post no api-budget notice, so the caller cannot measure them.

    Proposed cut: fold the needs-triage label job into the triage run and read each issue once, rather than reading it in both workflows. Estimated saving 60-150 core/h (UNVERIFIED). The trade-off is that labels land at the end of triage instead of at once.

    Also asked: have both reusables emit the same api-budget notice the caller's log-api-budget action writes (token=… limit= remaining= used= resource=), so the saving can be measured before and after.

    Sources: the attribution study (rows 1 and 3, "Cuts" row 3), and the call-reduction research, verified 2026-10-25: read github.event instead of re-fetching, and use per_page=100.

  3. kyle-sexton commented on Oct 25, 2026

    @kyle-sexton
    MemberAuthor

    One more change in the same budget area, raised from claude-code-plugins on 2026-10-25: check-key-credit (#882, v0.65.8) can't be reused by lanes that run far more often than the review lanes.

    The aim is to let claude-code-plugins' issue lane, pr-unblock (5 legs) and pr-refine skip the API-key leg after a credit stop and go straight to the subscription leg, as the review lanes already do. That saves about 5 runner jobs per run while credits are out, plus the second reusable-workflow call. Lines read at v0.65.8 in .github/actions/check-key-credit/gate.cjs:

    • :35 const MODEL_STEP = "Claude review"; is hard-coded, so another lane's model step is never matched.
    • :36 const MAX_JOB_READS = 3; caps the reads at the 3 newest runs. Skipped runs use up those reads without settling anything.
    • :66-74 jobNamed() matches job.name === name || job.name.endsWith(" / " + name), so a caller with several jobs named run matches more than one.
    • :89 decides "in window" by membership in the listed run ids (windowIds.has(...)). work-resolve-issue.yml has about 1,007 runs per 24 h, 94 of the last 100 skipped, so a carried record falls out of the list after about 2.4 h, not the configured 6 h.

    Proposed change, with defaults that keep today's review-lane behavior:

    1. Drop conclusion == "skipped" runs before the read cap. The run listing already carries conclusion, so this needs no extra call.
    2. Judge the window by the origin run's created_at, not by list membership.
    3. Add inputs for the model-step name and the exact key-job name.

    Changes 1 and 2 likely help the review lanes too. Once this is released, claude-code-plugins pins it, with the record step in the read runner's run job after act-key, where #882 itself places it.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageNot yet classified. Floor until a type and one priority tier are set.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions