fix(#779): recover from stale repo names in token requests - #819
fix(#779): recover from stale repo names in token requests#819fullsend-ai-coder[bot] wants to merge 2 commits into
Conversation
When config.yaml contains repos that were deleted, transferred, or
renamed, GitHub's POST /app/installations/{id}/access_tokens rejects
the entire batch with a 422. This blocked reconciliation for ALL
repos in the org, not just the stale ones.
Add retry-with-recovery to CreateInstallationToken: on a 422 with
a repositories validation error, parse the invalid repo names from
the GitHub error response, filter them out, and retry with only
the valid repos. The dropped repo names are surfaced in
GrantedScope.InvalidRepos and returned in the mint response as
invalid_repos so callers can detect and report stale config entries.
Key design decisions:
- Does NOT fall back to an installation-wide token when all repos
are invalid — returns a clear error instead
- Case-insensitive repo name matching for the filter
- Non-repo 422 errors (e.g. permissions issues) are not retried
but now include the response body for diagnostics instead of
discarding it
- Embedded GCF copies synced
Note: pre-commit could not run (sandbox network restrictions).
The post-script runs an authoritative pre-commit on the runner.
Closes #779
|
🤖 Finished Review · ✅ Success · Started 3:02 AM UTC · Completed 3:13 AM UTC |
ReviewFindingsLow
Previous runReviewFindingsMedium
Low
Labels: PR is a bug fix (fix prefix, linked to bug issue #779). Previous run (2)ReviewFindingsMedium
Low
|
|
/fs-review |
|
🤖 Finished Review · ✅ Success · Started 6:46 AM UTC · Completed 7:00 AM UTC |
|
/fs-fix |
|
🤖 Finished Fix · ✅ Success · Started 7:29 AM UTC · Completed 7:37 AM UTC |
- Use json.RawMessage for githubValidationErrorItem.Value to handle both []string and plain string forms GitHub may return in 422 errors - Validate parsed repo names against RepoNamePattern as defense-in-depth - Add design-rationale comment documenting the intentional asymmetry between fail-closed validation (role/provenance/org) and recovery semantics for repo validation - Add test cases for plain-string value form and invalid repo name filtering - Sync embedded GCF copy Addresses review feedback on #819
🔧 Fix agent — iteration 1 (human-triggered)Addressed 3 of 4 review findings: hardened Value field parsing to handle both []string and string forms via json.RawMessage, added RepoNamePattern validation as defense-in-depth, and documented the intentional asymmetry in recovery vs fail-closed validation. Disagreed with 1 finding (coordinating with operational tooling is out of scope for this PR). All changes synced to embedded GCF copy. Fixed (3):
Disagreed (1):
Tests: passedNext steps:
|
|
🤖 Finished Review · ✅ Success · Started 7:38 AM UTC · Completed 7:51 AM UTC |
There was a problem hiding this comment.
Note: The following inline comments could not be posted on the diff (GitHub returned 422) and are included here instead:
internal/mintcore/github.go:542: [low] defense-in-depth
parseInvalidRepos validates extracted repo names against RepoNamePattern but does not apply the strings.Contains(repo, "..") check that the handler applies to user-supplied repo names (handler.go:208). RepoNamePattern accepts names like '..traversal'. While the practical impact is near-zero (names from GitHub's API, only used for set-difference filtering), adding the check aligns with the handler's defense-in-depth approach.
Suggested fix: Add strings.Contains(n, "..") rejection in parseInvalidRepos alongside the RepoNamePattern check.
internal/mintcore/github.go:476: [low] architectural-conflict
The retry logic parses GitHub's 422 error response using structured JSON field matching (e.Field == 'repositories'). If GitHub changes the error format, parseInvalidRepos returns nil and the 422 is treated as a non-retryable error with the response body in the error message. Consider adding a log line when parseInvalidRepos returns nil on a 422-with-repos for observability.
Suggested fix: Add log.Printf when parseInvalidRepos returns empty results on a 422 response with repos.
internal/mintcore/handler.go(file-level): Line 665 · [low] coherence-with-issue
invalid_repos is exposed in the mint API response but no client-side follow-up is planned to act on it for config.yaml cleanup. Issue #779 asks about config pruning, but this PR addresses only mint-side recovery.
Suggested fix: File a follow-up issue for client-side cleanup of stale repos using the invalid_repos response field.
|
This pull request has been automatically marked as stale because it has not had any activity in the last month. It will be closed in 2 weeks if no further activity occurs. Remove the |
Summary
When
config.yamlcontains repos that were deleted, transferred, or renamed from the org, GitHub'sPOST /app/installations/{id}/access_tokensrejects the entire batch with a 422 — blocking reconciliation for ALL repos, not just the stale ones. This caused a real incident affecting ~129 repos.This PR adds retry-with-recovery to
CreateInstallationToken: on a 422 with arepositoriesvalidation error, the mint parses the invalid repo names from GitHub's error response, filters them out, and retries with only the valid repos. Invalid repos are surfaced in the response asinvalid_reposso callers can detect and report stale config entries.Changes
internal/mintcore/github.go: RefactoredCreateInstallationTokeninto a two-layer design:requestInstallationTokenhandles a single API call, whileCreateInstallationTokenorchestrates retry logic. On 422, parses GitHub's validation error to identify invalid repos, filters them out, and retries. AddedInvalidReposfield toGrantedScope,githubValidationErrortypes for parsing, andparseInvalidReposhelper.internal/mintcore/handler.go: Addedinvalid_reposfield tomintResponseand warning logging when repos are dropped.internal/mintcore/github_test.go: Added tests for retry with invalid repos, all-repos-invalid error, non-repo 422 errors, 422 without repos, andparseInvalidReposparsing.internal/mintcore/handler_test.go: Added end-to-end handler test for the full invalid-repo recovery flow.github.go.embedandhandler.go.embed.Testing
cmd/minttests passgo vet ./...passesTestEmbeddedMintSource_MatchesOriginal) passesparseInvalidReposedge casesServeHTTPCloses #779
Post-script verification
agent/779-mint-stale-repo-recovery)ba77dbdbbd17e9bb18c47bf6efa023c98b697158..HEAD)