anima-server: external_pr is bookkeeping, not a human decision #275

Merged
toasterson merged 1 commit from fix/external-pr-not-a-decision into main 2026-06-29 19:26:53 +00:00
Owner

Problem

The ProjectDetail right rail ("Needs Your Decision") and the dashboard "Pending Decisions" count were flooded — prod held 3,916 pending decision cards, ~100% noise:

kind sub-kind count what it is
external_pr agent_diff_applied 2,018 "pushed agent diff onto the PR branch"
external_pr forgejo_pr 930 "a PR was opened"
external_pr solstice_dispatch 314 "CI dispatched"
external_pr solstice_result 293 "CI result recorded"
agent_diff 361 genuine, but frozen since the agent path was retired

One WI ("OTel logs") alone had 195 identical cards — same branch, same PR, same SHA — inserted every ~15 min by the triage cron.

Root cause

ChangeKind::requires_review() listed ExternalPr, so every external_pr row (a triage-cron telemetry marker re-emitted once per tick per open PR) was born pending and surfaced as a perpetual decision card. Unlike agent_diff, it got no supersede_prior_pending, so the cards never drained — exactly the 2026-06-09 merge-loop trap the requires_review doc comment warns about.

Fix (three aligned spots — one definition of "what is a decision")

  • ChangeKind::requires_review() drops ExternalPr → born supersede, never a card. + regression test locking the review set.
  • Dashboard pending-decisions SQL drops 'external_pr' from its hardcoded mirror.
  • list_pending_for_project switches the loose kind <> 'wip_snapshot' to an explicit allowlist so a future bookkeeping kind can't leak the same way.

Genuine human merge decisions remain agent_diff / human_patch / companion_proposal. The PR marker is still recorded — find_pr_ref reads it by kind regardless of decision — just no longer as a pending card.

The 3,916 existing rows are drained separately by a one-off prod UPDATE ... SET integration_decision='supersede'.

🤖 Generated with Claude Code

## Problem The ProjectDetail right rail ("Needs Your Decision") and the dashboard "Pending Decisions" count were flooded — prod held **3,916** pending decision cards, ~100% noise: | kind | sub-kind | count | what it is | |---|---|---|---| | `external_pr` | `agent_diff_applied` | 2,018 | "pushed agent diff onto the PR branch" | | `external_pr` | `forgejo_pr` | 930 | "a PR was opened" | | `external_pr` | `solstice_dispatch` | 314 | "CI dispatched" | | `external_pr` | `solstice_result` | 293 | "CI result recorded" | | `agent_diff` | — | 361 | genuine, but frozen since the agent path was retired | One WI ("OTel logs") alone had **195 identical cards** — same branch, same PR, same SHA — inserted every ~15 min by the triage cron. ## Root cause `ChangeKind::requires_review()` listed `ExternalPr`, so every `external_pr` row (a triage-cron telemetry marker re-emitted once per tick per open PR) was born `pending` and surfaced as a perpetual decision card. Unlike `agent_diff`, it got no `supersede_prior_pending`, so the cards never drained — exactly the 2026-06-09 merge-loop trap the `requires_review` doc comment warns about. ## Fix (three aligned spots — one definition of "what is a decision") - `ChangeKind::requires_review()` drops `ExternalPr` → born `supersede`, never a card. + regression test locking the review set. - Dashboard pending-decisions SQL drops `'external_pr'` from its hardcoded mirror. - `list_pending_for_project` switches the loose `kind <> 'wip_snapshot'` to an explicit allowlist so a future bookkeeping kind can't leak the same way. Genuine human merge decisions remain `agent_diff` / `human_patch` / `companion_proposal`. The PR marker is still recorded — `find_pr_ref` reads it by `kind` regardless of decision — just no longer as a pending card. The 3,916 existing rows are drained separately by a one-off prod `UPDATE ... SET integration_decision='supersede'`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
anima-server: external_pr is bookkeeping, not a human decision
Some checks failed
CI / snapshot (push) Has been skipped
CI / conflict-check (push) Failing after 2h0m29s
CI / docker (push) Failing after 2h0m28s
c2c20e45b7
Every `external_pr` WorkItemChange is a triage-cron telemetry marker —
PR opened, agent diff pushed, CI dispatched, CI result — re-emitted once
per ~15-min tick per open PR. But `ChangeKind::requires_review()` listed
ExternalPr, so each marker was born `pending` and surfaced as a perpetual
"Needs Your Decision" card. With no supersede-prior-pending (only
agent_diff got that), they never drained: by 2026-06-29 prod held 3,555
phantom external_pr decision cards (195 on a single WI), exactly the
2026-06-09 merge-loop trap the requires_review doc comment warns about.

Fix, three aligned spots so "what is a decision" stays one definition:
- ChangeKind::requires_review() drops ExternalPr → born `supersede`,
  never a card. (+ regression test locking the review set.)
- dashboard pending-decisions SQL drops 'external_pr' from its mirror.
- list_pending_for_project switches `kind <> 'wip_snapshot'` to an
  explicit allowlist so a future bookkeeping kind can't leak the same way.

Genuine human merge decisions remain agent_diff / human_patch /
companion_proposal. The PR marker is still recorded (find_pr_ref reads it
by kind regardless of decision), just no longer as a pending card.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
toasterson deleted branch fix/external-pr-not-a-decision 2026-06-29 19:26:53 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
toasterson/Anima!275
No description provided.