fix: prevent tasks from being associated with the wrong PR#40
Merged
Conversation
genisd
force-pushed
the
fix/pr-association-validation
branch
from
July 8, 2026 09:25
6a074fb to
d2e336f
Compare
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
Prod incident (2026-07-08, task
347c7c88): a task was associated with the wrong PR (gynzy/backend-python#453 instead of jonwiggins#506) and instantly auto-completed. The task's prompt contained a literal example PR URL; when the exec stream dropped mid-run, the worker regex-scanned the log, took the first PR-URL match (the prompt's example), and since that PR was merged weeks earlier the reconciler completed the task 2 seconds later — while the agent was still working and opened the real PR 90 seconds after that.This PR adds three independent defenses so a PR URL merely appearing in agent logs can no longer hijack a task's PR association.
Changes
packages/agent-adapters): all six adapters now take the last PR-URL match in the log instead of the first — prompts and examples come first, PR-creation output comes last.pr_opened(apps/api): newvalidateTaskPrUrlfetches the candidate PR and requires its head branch to be the task's deterministic branchoptio/task-<taskId>. Invalid candidates are discarded so the existing branch-basedcheckExistingPrfallback finds the real PR or escalates toneeds_attention. Fails open when the platform API is unavailable, so an outage can't make detection worse than before. Supporting this, the sharedPullRequesttype gains aheadBranchfield (GitHubhead.ref, GitLabsource_branch).packages/shared/src/reconcile):decideFromPrStatusnow refuses to act on a PR created before the task existed — routes toneeds_attention(pr_predates_task) instead of auto-completing/failing. Onlycodingtasks are guarded:reviewandpr_reviewtasks legitimately point at PRs that predate them; missing PR timestamps skip the guard;failedtasks noop (invalid transition).flowchart LR A[agent log scan] -->|candidate URL| B{head branch ==\noptio/task-id?} B -->|valid| C[pr_opened] B -->|invalid| D[checkExistingPr\nby branch] B -->|API unavailable| C D -->|found| C D -->|none| E[needs_attention] C --> F{reconciler:\nPR predates task?} F -->|no| G[normal PR watching] F -->|yes| EKnown follow-ups (out of scope): the mid-stream log scanner is still first-match and unvalidated (the reconciler guard neutralizes the dangerous outcome), a discarded bogus URL can remain visible in
tasks.prUrlon escalated tasks, and the exec-stream drop that triggered the incident is a separate reliability issue.Testing
Incident-shaped scenarios covered by new unit tests: last-match selection in every adapter, all three
validateTaskPrUrlverdicts, and the reconciler guard incl. exemptions (review/pr_review, missing timestamp,failednoop).pnpm turbo test)pnpm turbo typecheck)Related
Plan with full incident analysis:
docs/superpowers/plans/2026-07-08-pr-association-validation.mdScreenshots
n/a