Implement GitHub token auto-refresh with file-based credentials cache#11
Merged
Implement GitHub token auto-refresh with file-based credentials cache#11
Conversation
GitHub App installation access tokens expire after 1 hour, causing 401 "Bad credentials" errors in long-running CI jobs. This change implements automatic token refresh with a file-based cache shared across processes. Changes: - Add expiresAt field to GithubClient to track token expiration - Implement credentials cache file (.github-token-cache.json) for sharing tokens between parent and child processes - Auto-refresh tokens when they expire within 5 minutes - Remove old environment variable approach - Add test infrastructure for configurable token lifetime - Add new test verifying token refresh after expiration The cache file stores tokens with ISO8601 expiration timestamps and uses file locking to prevent concurrent refresh. Child processes read from the cache, and any process can refresh if the token is expired. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Replace mixed SHARED/EXCLUSIVE locking approach with a single EXCLUSIVE lock for all credential operations. This eliminates the need for double-check locking and simplifies the code significantly. Changes: - Remove readCredentialsCache and writeCredentialsCache functions - Add loadOrRefreshClient that acquires EXCLUSIVE lock for entire operation - Add tryReadCache helper (no locking, caller holds lock) - Add refreshToken helper (creates token and writes cache under lock) - Refactor initClient → createTokenFromGitHub (just creates token) - Fast path in getClient checks IORef without locks Benefits: - No double-check locking needed - Prevents thundering herd (only one process refreshes at a time) - No torn reads (all file operations under EXCLUSIVE lock) - Simpler code with clear locking boundaries - Lock duration is brief (~100ms for HTTP call) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
The test was calling snapshot twice within a single task, which is illegal (snapshot writes to a single IORef that can only hold one value). Fixed by removing the second snapshot call and relying on the automatic final status update when the task completes. Test flow now: 1. snapshot posts "pending" status (creates and caches token) 2. Task sleeps 3 seconds (token expires after 2 seconds) 3. Task completes, final "success" status is posted automatically (detects expired cached token and refreshes it) This properly tests token refresh while following the one-snapshot-per-task rule. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
The hardcoded 300-second (5-minute) refresh threshold was causing the github-token-refresh test to fail. With a 2-second token lifetime, fresh tokens were immediately considered "expiring" (2s < 300s threshold), triggering unnecessary refreshes on every getClient() call. Changes: - Add githubTokenRefreshThresholdSeconds to Settings (default: 300) - Read TASKRUNNER_GITHUB_TOKEN_REFRESH_THRESHOLD_SECONDS env var - Use configurable threshold in getClient and loadOrRefreshClient - Set threshold to 1 second in github-token-refresh test This allows the test to verify proper token refresh behavior: - Fresh tokens (2s remaining >= 1s threshold) are reused - Expired tokens trigger refresh - Test now passes with 2 token requests instead of 4 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
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.
Summary
Fixes 401 "Bad credentials" errors in long-running CI jobs by implementing automatic GitHub App token refresh with file-based credential caching.
Problem
GitHub App installation access tokens expire after 1 hour. Previously, taskrunner created a token once at startup and cached it in an environment variable. For CI jobs longer than 1 hour, subsequent GitHub API calls would fail with:
Solution
Implemented automatic token refresh with the following components:
File-Based Credentials Cache
{stateDirectory}/.github-token-cache.jsonAuto-Refresh Logic
Changes Made
expiresAt: UTCTimetoGithubClient# github token lifetime Ntest directiveTest Plan
github-token-refreshverifies token refresh after expiration🤖 Generated with Claude Code