fix(agent-actions): re-verify live CI before a merge or heuristic close - #2364
Conversation
The freshness guard in executeAgentMaintenanceActions re-checks head SHA and PR state before every live action, but not CI. The CI aggregate that drove a heuristic close or a merge decision is read once in the planning pass, seconds-to-tens-of-seconds before actuation, and never re-read at the moment of mutation. GitHub's merge endpoint enforces branch-protection required checks server-side only as a backstop when a repo configures them, and a heuristic close has no server-side check at all — unlike the deterministic linked-issue-hard-rule close, a heuristic CI-driven close has no flag-then-verify pass either. Add a new guard step that re-derives live CI via the existing fetchLiveCiAggregate helper immediately before a merge or a heuristic close (closeKind: "heuristic"), and denies the action if a merge's CI has since turned failed, or a close's CI is no longer failed. Deterministic closes (linked-issue hard-rule, blacklist) are exempt. Best-effort: a token-mint failure fails open, since this is a defense-in-depth check, not the primary gate (the freshness check above it already fails closed on an unverifiable PR state).
|
Caution 🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥 🛑 Gittensory review result - fixes requiredReview updated: 2026-07-01 20:27:51 UTC
🛑 Suggested Action - Manual Review
Review summary Nits — 6 non-blocking
CI checks failing
Review context
Contributor next steps
Signal definitions
🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2364 +/- ##
=======================================
Coverage 95.82% 95.83%
=======================================
Files 224 224
Lines 24975 25006 +31
Branches 9076 9096 +20
=======================================
+ Hits 23933 23964 +31
Misses 428 428
Partials 614 614
🚀 New features to boost your workflow:
|
…on-failed The planner itself only ever stages a merge when ciState === "passed" exactly (reviewGood in agent-actions.ts; "pending" short-circuits to no actions at all upstream). The live re-check before actuation only denied on ciState === "failed", so a check that regressed to pending or became unreadable (unverified) between planning and actuation still let the merge proceed on stale information -- exactly the class of gap this guard exists to close. Require the same exact "passed" state the planner itself requires. Also fixes test/unit/routes-agent-approval.test.ts, which pre-dates this PR's live-CI-recheck step entirely and never mocked fetchLiveCiAggregate -- its accept-merge happy path was incidentally passing only because the un-mocked call fell through to "unverified", which the too-lenient original check treated as fine. Mock it to "passed" like the executor's own test file already does.
…ive-CI recheck survives replay actionParams() didn't round-trip closeKind, so pendingActionToPlanned() rebuilt an accepted approval-queue close with closeKind undefined -- silently skipping the actuation-time live CI re-check this PR adds, since that check keys on action.closeKind === "heuristic".
# Conflicts: # test/unit/agent-approval-queue.test.ts # test/unit/routes-agent-approval.test.ts
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
gittensory-ui | e0b9ebc | Commit Preview URL Branch Preview URL |
Jul 01 2026, 08:21 PM |
A merge landed the same closeKind field twice in the same type literal — once from #2127 (close-precision circuit-breaker scoping) and once from this PR's own #2364 (actuation-time live-CI re-check) — causing TS2300 "Duplicate identifier 'closeKind'" and failing validate-code. Collapse to a single declaration with a merged comment explaining both consumers.
Same merge-conflict-resolution artifact as the AgentPendingActionParams type duplicate: closeKind was spread into the persisted params twice (once bare, once with the #2127 explanatory comment). Both computed the identical value, so this was harmless at runtime, but redundant and confusing. Keep the single, commented copy and note the #2364 live-CI re-check's dependency on it too.
What
The freshness guard in
executeAgentMaintenanceActionsre-checks head SHA and PR state before every live action, but not CI. The CI aggregate that drove a heuristic close or a merge decision is read once in the planning pass, seconds-to-tens-of-seconds before actuation, and never re-read at the moment of mutation. GitHub's own merge endpoint enforces branch-protection required checks server-side, but only as a backstop when a repo actually configures them — and a heuristic close has no server-side check at all. A required check that flips green in the narrow window between planning and actuation could still get a PR closed on stale information: unlike the deterministic linked-issue-hard-rule close, a heuristic CI-driven close has no flag-then-verify pass.Fix
Add a new guard step to
executeAgentMaintenanceActions(src/services/agent-action-executor.ts), right after the existing freshness guard: immediately before amergeor a heuristic close (closeKind: "heuristic"), re-derive live CI via the existingfetchLiveCiAggregatehelper and deny the action if:"failed"(a regression since planning), or"failed"(the failure that justified the close no longer holds — pending/passed/unverified all invalidate it).Deterministic closes (linked-issue hard-rule, blacklist) are exempt — they're zero-hallucination facts that don't depend on CI, and the linked-issue rule already has its own flag-then-verify pass. Best-effort: a token-mint failure fails open, since this is a defense-in-depth check, not the primary gate — the freshness check immediately above it already fails closed on an unverifiable PR state.
Tests
fetchLiveCiAggregatenot called).Full unsharded
test:coveragegreen;typecheckgreen;npm auditclean.Advances #1936. Closes #2128.