gh-pulse: throttle the hour (0.36.0) - #72
Merged
Merged
Conversation
A run died this morning with GitHub 403, rate limit exceeded, ten repos into the events step, and the spinner kept ticking after the error because the queue went on draining in the background. Four range scans and a daily run in one hour had spent the whole 5,000-call budget the token shares with gh and everything else, and the client's answer to a 403 was to wait at most two minutes, four times, then throw. The GitHub client is now the only place the hour is spent, and it behaves. GH_PULSE_RESERVE calls (default 500) are never spent: down to the reserve, or rate limited anyway, a scan pauses until the hour resets, on one shared pause for every in-flight request, for as long as the headers say, and then carries on. Four requests in flight and 100ms between starts keep it out of the secondary limits; retry-after is honoured. Every reply is cached by ETag under <data>/cache and offered back with If-None-Match, and GitHub does not charge for a 304, so an unchanged page is free; since= is asked from the top of the hour so the URL repeats and a rescan hits the cache, with the exact cutoff applied to the items. Once a task has failed nothing queued behind it starts, and show exits the process when the screen closes, so a crash or a quit no longer spends the rest of the hour in the background. One daily run at a time, through <data>/run.lock. The budget is read off the first real reply's headers, not /rate_limit, which reported a fresh hour for this token while real replies carried 4,695 left. Each scan logs what it has to spend and closes with what was charged, what came from cache and what is left. Measured live on one repo for the last hour: a cold scan charged 20 calls, the warm rescan 0, with 26 answered from cache. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012nnVu9u7HeHEVRoCwKcQM3
ThreatCrush Security Scan13 finding(s) HIGH/CRITICAL: 4 | MEDIUM: 5 | LOW: 4
Snippets are redacted; ThreatCrush never prints matched credential material. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong
A
gh-pulserun this morning died withGitHub 403: API rate limit exceededten repos into the events step, and the spinner kept ticking after the error because the request queue went on draining in the background. Four range scans and a daily run in one hour had spent the whole 5,000-call budget the token shares withghand every other tool, and the client's answer to a 403 was to wait at most two minutes, four times, then throw.What changes
The
GitHubclient is now the only place the hour is spent, and it behaves:GH_PULSE_RESERVEcalls of the hour (default 500) are never spent. Down to the reserve, or rate limited anyway, the scan pauses until the hour resets and then carries on, on one shared pause for every in-flight request, for as long as the headers say. The pause is announced on the spinner line and in the log.retry-afteris honoured.<data>/cacheby ETag and offered back withIf-None-Match. GitHub does not charge for a 304, so an unchanged page is free. Entries nobody has asked about for a month are pruned after each daily run.since=from the top of the hour. The commits and issues URLs repeat within an hour, so a rescan hits the cache; the exact cutoff is applied to the items.gh-pulse showexits the process when the screen closes for the same reason.<data>/run.locknames the pid; a second run refuses while the first is alive and takes over a stale lock./rate_limitreported a fresh hour for this token while real replies said 4,695 left, so it is not consulted), and closes with what was charged, what came from cache, and what is left.Measured
Live,
--range hour --repo profullstack/g1tz, from the worktree:With
since=floored the warm run is 0 charged.Tests
test/gh-pulse-client.test.ts(16 tests, fake clock, scripted fetch): spacing, the reserve pause, a 403 waited out on one shared pause,retry-after, a 403 that is not about the limit, the ETag round trip, cache keyed by accept header, no cache dir, pruning, the reserve from the environment, abort of queued work, the budget line,sinceflooring with the cutoff applied, and the run lock. Existing gh-pulse suites unchanged and green. The one failing test in the full suite (torrent.test.ts, needs a live client) is unrelated.🤖 Generated with Claude Code
https://claude.ai/code/session_012nnVu9u7HeHEVRoCwKcQM3