Skip to content

Route stale-PR follow-up by provider so merged GitLab MRs poll correctly#242

Merged
dhilgaertner merged 1 commit intomainfrom
feature/crow-241-merged-pr-polling
May 5, 2026
Merged

Route stale-PR follow-up by provider so merged GitLab MRs poll correctly#242
dhilgaertner merged 1 commit intomainfrom
feature/crow-241-merged-pr-polling

Conversation

@dhilgaertner
Copy link
Copy Markdown
Contributor

Summary

  • IssueTracker.fetchStalePRStates hardcoded gh api graphql for every stale PR URL. One GitLab MR in the batch (e.g. big-bang/product on repo1.dso.mil) failed the gh call, returned nil, and wiped the whole cycle's merged-state update — so merged PRs (GitHub or GitLab) only turned purple on app launch.
  • Split URLs by provider: GitHub PRs → existing batched aliased GraphQL via gh; GitLab MRs → one glab api projects/{slug}/merge_requests/{iid} per MR with GITLAB_HOST set per host (mirroring the pattern in fetchGitLabMRsForReconcile).
  • Replaced the [ViewerPR]? return with a StalePRFetchResult { prs, complete } struct so a single-provider failure no longer masks the other provider's PRs — partial success contributes its PRs to applyPRStatuses while prDataComplete=false still blocks auto-completion on degraded data.
  • Added a robust GitLab MR URL parser (parseGitLabMRURL) that handles nested groups, plus a shared normalizeGitLabPRState helper used by both stale-fetch and reconcile.

Closes #241

Test plan

  • make build — clean
  • swift test --filter IssueTrackerStalePRTests — 17 new tests pass (URL parse, state normalization, JSON parse, mixed-provider dedup)
  • swift test — all IssueTracker suites green; one pre-existing flaky tmuxBackendDefaultIsOff race unrelated to this change
  • Manual: with both a merged GitHub PR and a merged GitLab MR linked to active sessions, wait one poll cycle (≤60s) without restarting the app — both badges flip to purple and no [IssueTracker] Stale-PR follow-up failed (exit 1) log lines appear

🤖 Generated with Claude Code

The 60s polling cycle was hardcoding `gh api graphql` for every stale PR
URL — including GitLab MRs. One GitLab URL in the batch failed the whole
gh call (`Could not resolve to a Repository with the name 'big-bang/product'`),
so `fetchStalePRStates` returned nil and no PR (GitHub or GitLab) got its
merged state applied. That's why merged PRs only flipped to purple on
app launch.

Split URLs by provider: GitHub PRs go through the existing batched aliased
GraphQL query; GitLab MRs go through one `glab api projects/{slug}/merge_requests/{iid}`
per MR with `GITLAB_HOST` set per host. Return a small `StalePRFetchResult`
struct so a single-provider failure no longer masks the other provider's
PRs — partial-success now flows through to `applyPRStatuses` while
`prDataComplete=false` still blocks auto-completion on degraded data.

Closes #241
Copy link
Copy Markdown
Collaborator

@dgershman dgershman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code & Security Review

Critical Issues

None found.

Security Review

Strengths:

  • URL-encoded slug passed to the GitLab API endpoint (addingPercentEncoding(withAllowedCharacters: .alphanumerics)) prevents path traversal via malicious slug strings.
  • Error output is truncated (.prefix(200)) in log messages, preventing large stderr from flooding logs.
  • GITLAB_HOST is set per-host in the process environment, mirroring the existing fetchGitLabMRsForReconcile pattern — no credential leak across hosts.
  • NSHomeDirectory() as the cwd avoids the non-repo-cwd failure that #233 fixed.

Concerns:

  • None. The attack surface is limited to locally-stored session URLs that originate from the user's own linked PRs.

Code Quality

Well done:

  • The StalePRFetchResult struct cleanly replaces the [ViewerPR]? optional with explicit partial-success semantics — this is a meaningful improvement in clarity over nil-means-failure.
  • Extracting normalizeGitLabPRState and reusing it in fetchGitLabMRsForReconcile eliminates the inline switch duplication.
  • parseGitLabMRURL handles nested groups correctly (the two-segment heuristic in parseTicketURLComponents would truncate), and the test suite explicitly guards this regression.
  • The host guard at line 699 (host != "github.com") is a sensible safety net — unknown hosts that don't match the /-/merge_requests/ pattern won't get blindly routed to gh.
  • Test coverage is comprehensive: URL parsing (top-level, nested, rejection cases), state normalization, JSON parsing (merged/opened/closed/malformed/legacy WIP flag), and dedup integration.

Minor observations (non-blocking):

  • The for (host, parsed) in gitlabParsedByHost loop at line 721 runs sequentially. For the stated assumption ("stale set is usually tiny") this is fine, but if it ever grows, these could be parallelized with a TaskGroup since they're independent per-host.
  • parseGitLabStaleMRResponse returns nil (marking the cycle incomplete) on unparseable JSON, but this could also happen if the MR was deleted (404 returning an error body). The error-vs-parse-failure distinction is already handled by the try/catch in the caller, so this is fine in practice.

Summary Table

Priority Issue
Green Sequential per-host GitLab fetches are fine for current scale; consider TaskGroup if stale sets grow
Green 404 vs. malformed JSON both result in ok = false — correct behavior, just worth noting for future debugging

Recommendation: Approve. The change is well-structured, correctly isolates provider failures so one bad GitLab host can't suppress GitHub PR updates, and the test suite thoroughly covers the new parsing/normalization logic. The architecture matches existing patterns in the codebase (reconcile path) and the PR description accurately reflects what was done.

@dhilgaertner dhilgaertner merged commit a6633f2 into main May 5, 2026
3 checks passed
@dhilgaertner dhilgaertner deleted the feature/crow-241-merged-pr-polling branch May 5, 2026 00:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Merged PRs not turning purple on periodic GitHub polling (only on app open); IssueTracker error for 'big-bang/product'

2 participants