feat(github): add fetchPullRequestForBranch helper#2873
Conversation
Adds a small Octokit helper that looks up the PR associated with a (repo, branch) pair using an existing GitHub App installation token. Used exclusively by the manual 'Refresh PR info' mutation — the common case is webhook-driven. Intentionally no caching or in-flight dedup since the caller is throttled to once per 10s per session. - Returns most recently updated PR whose head ref matches, preferring open PRs when multiple exist. - Maps merged_at != null to a 'merged' state. - Throws GitHubRateLimitError on 403/429 or x-ratelimit-remaining=0, surfacing the parsed reset timestamp so callers can report it. - Returns null on 404 (repo no longer accessible / deleted). Bead 1 of 5 in the 'Associated PR for cloud-agent-next session' convoy.
|
Refinery code review passed. Scope check: Matches bead 1 acceptance — Correctness:
Tests: Sibling test file exercises all acceptance cases (single-open, open-preferred, merged mapping, no-open fallback, empty → null, 404 → null, 403/429/header-only rate-limit, unknown error passthrough). The relative-import trick to bypass the global Mock adapter: Updated with no-op stubs for Minor (non-blocking): No security concerns — no logged tokens, no bypassed auth, no unsafe patterns. Good to merge once bead 3 consumes it. |
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Files Reviewed (3 files)
Reviewed by gpt-5.5-20260423 · 494,973 tokens |
Non-rate-limit 403 responses (e.g. installation missing pull request
permission) were being wrapped as GitHubRateLimitError, which would
incorrectly tell users to retry instead of surfacing the permission
problem. Now only treat 403 as rate-limited when the x-ratelimit
headers or the error message ('rate limit', 'secondary rate limit',
'abuse') indicate so. 429 remains unambiguously rate-limited, and
x-ratelimit-remaining: 0 still triggers the rate-limit path at any
status.
3e32e34
into
convoy/associated-pr-for-cloud-agent-next-sessi/dbccdbdf/head
* feat(github): add fetchPullRequestForBranch helper
Adds a small Octokit helper that looks up the PR associated with a
(repo, branch) pair using an existing GitHub App installation token.
Used exclusively by the manual 'Refresh PR info' mutation — the common
case is webhook-driven. Intentionally no caching or in-flight dedup
since the caller is throttled to once per 10s per session.
- Returns most recently updated PR whose head ref matches, preferring
open PRs when multiple exist.
- Maps merged_at != null to a 'merged' state.
- Throws GitHubRateLimitError on 403/429 or x-ratelimit-remaining=0,
surfacing the parsed reset timestamp so callers can report it.
- Returns null on 404 (repo no longer accessible / deleted).
Bead 1 of 5 in the 'Associated PR for cloud-agent-next session' convoy.
* fix(github): only classify 403 as rate limit when message indicates it
Non-rate-limit 403 responses (e.g. installation missing pull request
permission) were being wrapped as GitHubRateLimitError, which would
incorrectly tell users to retry instead of surfacing the permission
problem. Now only treat 403 as rate-limited when the x-ratelimit
headers or the error message ('rate limit', 'secondary rate limit',
'abuse') indicate so. 429 remains unambiguously rate-limited, and
x-ratelimit-remaining: 0 still triggers the rate-limit path at any
status.
---------
Co-authored-by: Maple (gastown) <Maple@gastown.local>
* feat(github): add fetchPullRequestForBranch helper
Adds a small Octokit helper that looks up the PR associated with a
(repo, branch) pair using an existing GitHub App installation token.
Used exclusively by the manual 'Refresh PR info' mutation — the common
case is webhook-driven. Intentionally no caching or in-flight dedup
since the caller is throttled to once per 10s per session.
- Returns most recently updated PR whose head ref matches, preferring
open PRs when multiple exist.
- Maps merged_at != null to a 'merged' state.
- Throws GitHubRateLimitError on 403/429 or x-ratelimit-remaining=0,
surfacing the parsed reset timestamp so callers can report it.
- Returns null on 404 (repo no longer accessible / deleted).
Bead 1 of 5 in the 'Associated PR for cloud-agent-next session' convoy.
* fix(github): only classify 403 as rate limit when message indicates it
Non-rate-limit 403 responses (e.g. installation missing pull request
permission) were being wrapped as GitHubRateLimitError, which would
incorrectly tell users to retry instead of surfacing the permission
problem. Now only treat 403 as rate-limited when the x-ratelimit
headers or the error message ('rate limit', 'secondary rate limit',
'abuse') indicate so. 429 remains unambiguously rate-limited, and
x-ratelimit-remaining: 0 still triggers the rate-limit path at any
status.
---------
Co-authored-by: Maple (gastown) <Maple@gastown.local>
Summary
fetchPullRequestForBranch,AssociatedPullRequest, andGitHubRateLimitErrorexports inapps/web/src/lib/integrations/platforms/github/adapter.ts.${owner}:${branch}, preferring open PRs when multiple exist; mapsmerged_at != nullto'merged'.x-ratelimit-remaining: 0) throwsGitHubRateLimitErrorwith the parsedx-ratelimit-resettimestamp; on 404 returnsnullwith a warning log.refreshAssociatedPullRequestmutation.Verification
fetch-pull-request-for-branch.test.tsmocks@octokit/restand@octokit/auth-appand covers: single open PR happy path, open-preferred-over-closed, merged state mapping, fallback to first when no open, empty result → null, 404 → null, 403/429 →GitHubRateLimitErrorwith correctresetAt, header-only rate-limit signal, and passthrough of unexpected errors. Tests bypass the global adapter mock (moduleNameMapperentry) by importing via relative path.pnpm testcould not execute (globalSetup requires Postgres). Typecheck/lint not run per town policy ("Don't run the linters in the cloud repo … Don't run the typechecker unless you absolutely have to."). CI will validate.Visual Changes
N/A
Reviewer Notes
__mocks__mirror atapps/web/src/tests/setup/__mocks__/lib/integrations/platforms/github/adapter.tswas updated with no-op stubs for the new exports so tests that import the mocked adapter still typecheck.x-ratelimit-remaining: 0) in addition to status codes, so GitHub's secondary-rate-limit quirks are covered.parseRateLimitResetAtfalls back tonow + 60sif the reset header is missing/invalid so callers always have a usableDate.moduleNameMapperadapter mock is the only way to exercise the real implementation without invalidating the mock for every other test file.