Require positive evidence before auto-completing sessions (#181)#182
Merged
dhilgaertner merged 1 commit intomainfrom Apr 22, 2026
Merged
Conversation
IssueTracker.autoCompleteFinishedSessions used set-difference against the fetched openIssues list, so a degraded/partial GraphQL response silently flipped every candidate session to completed. Observed 2026-04-16 when three active sessions with still-open issues were all completed in one refresh cycle. - fetchStalePRStates now returns [ViewerPR]? (nil = fetch errored), letting the refresh flow tell an empty success from a silent failure. - refresh() threads a prDataComplete flag and closedIssueURLs into the auto-complete paths. - decideSessionCompletions / decideReviewCompletions are new pure nonisolated statics. PR-linked completion requires the PR in prsByURL with state MERGED or CLOSED; issue-only completion requires the ticket URL in closedIssueURLs. Both absence-based fallbacks removed. - Floor guard: if any candidate has a ticket URL and openIssueURLs is empty, auto-complete skips the cycle and logs a warning. - autoCompleteFinishedSessions / autoCompleteFinishedReviews become thin adapters that read from AppState and apply the decisions. - 14 new tests in IssueTrackerCompletionTests cover the floor guard, positive-evidence rules, partial-fetch case, and review completion. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dgershman
approved these changes
Apr 22, 2026
Collaborator
dgershman
left a comment
There was a problem hiding this comment.
Code & Security Review
Security Review
Strengths:
- No user-facing input handling, injection surfaces, or secrets in the changed code
- The change strictly tightens trust boundaries — removing an unsafe absence-based inference and replacing it with positive evidence checks
Concerns:
- None
Code Quality
Strengths:
- Clean extraction of decision logic into
nonisolated staticpure functions (decideSessionCompletions,decideReviewCompletions) — testable without mockingAppStateor shell calls. Good pattern that matches existingdedupedByURL/mergePRRecordsconvention. fetchStalePRStatesreturn type change from[ViewerPR]to[ViewerPR]?is the right primitive — threadingprDataCompletethrough callers is clean and explicit.CompletionDecisionandCompletionResulttypes carry intent (reason string, floor guard flag) without coupling to side effects.- 14 new tests cover the full matrix: floor guard trigger/non-trigger, MERGED/CLOSED/OPEN PR states, partial fetch, missing-from-payload, issue-only paths, review sessions, and the original regression scenario.
- Comments explain the why (partial GraphQL responses, the 2026-04-16 incident) not just the what.
Minor observations (non-blocking):
- The floor guard at
IssueTracker.swift:1067-1069has a known false-positive: a user with genuinely zero open issues but active sessions with ticket URLs will skip the cycle. The PR description calls this out explicitly and it's the safer default — no action needed. autoCompleteFinishedReviewsfiltersappState.sessionsfor.review+.activebut the old code also only looked at active reviews, so this is consistent. Worth noting that.pausedor.inReviewreview sessions won't auto-complete — seems intentional.
Summary Table
| Priority | Issue |
|---|---|
| 🟢 | Floor guard false-positive on zero-open-issues edge case (acknowledged, safe default) |
| 🟢 | Review sessions with .paused/.inReview status excluded from auto-complete (consistent with old behavior) |
Recommendation: Approve — this is a well-scoped, well-tested fix for a real production bug. The positive-evidence requirement is strictly safer than the old absence-based logic, and the belt-and-suspenders floor guard adds an extra layer of protection against future regressions.
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.
Closes #181.
Summary
autoCompleteFinishedSessionsused set-difference againstopenIssues, so a silently-partial GraphQL response (rate-limit, parse miss, stale-PR fetch error) flipped every candidate session to completed. Replace the two absence-based fallbacks with positive-evidence rules: PR-linked sessions needMERGED/CLOSEDinprsByURL; issue-only sessions need the ticket URL inclosedIssueURLs.fetchStalePRStatesnow returns[ViewerPR]?(nil on shell/parse failure) andrefresh()threadsprDataComplete+closedIssueURLsinto both auto-complete paths. When the stale-PR fetch errors, PR-linked completion is skipped.openIssueURLsis empty, skip the cycle with a warning.nonisolated statichelpers (decideSessionCompletions,decideReviewCompletions) so it's covered by unit tests without a shell/Process abstraction (matches thededupedByURL/mergePRRecordspattern from Fix IssueTracker duplicate-key crash on PR status refresh #180). The same guards apply symmetrically to review-session completion.Test plan
make build— clean compileswift test --filter IssueTracker— 20/20 pass (6 existing dedup + 14 new)swift test(full suite) — all passrunConsolidatedGitHubQueryto return an emptydata.openIssues.nodesand confirm no active sessions flip + log lineskipping auto-complete — openIssues empty with N candidate sessionsBehavior changes worth calling out
closed:>=window in the consolidated query) will no longer auto-complete — the user marks them manually. Matches the ticket's "positive evidence only" requirement.MERGEDorCLOSED. Previously, "no longer in the review-request queue for any reason" triggered completion (e.g., reviewer unassigned with PR still open). Users will manually complete those cases.Recovery for the three sessions flipped on 2026-04-16 is unchanged from the ticket:
🤖 Generated with Claude Code