Skip to content

fix(agent-actions): pin staged close actions to a head SHA and re-verify live state at accept time - #2477

Merged
JSONbored merged 3 commits into
mainfrom
fix/pin-close-actions-head-sha-2452
Jul 2, 2026
Merged

fix(agent-actions): pin staged close actions to a head SHA and re-verify live state at accept time#2477
JSONbored merged 3 commits into
mainfrom
fix/pin-close-actions-head-sha-2452

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

  • Staged close actions (blacklist, linked-issue-hard-rule, heuristic) never had expectedHeadSha set, unlike approve/merge. The accept-time force-push guard in agent-approval-queue.ts only fires when a pin exists and disagrees with the live head — an unpinned close's freshness check instead falls back to comparing the live head against itself (both sides derive from the same fresh getPullRequest read at the top of the accept flow), so it could never actually catch a force-push that happened during the queue wait.
  • Pins expectedHeadSha on all three close-construction sites in src/settings/agent-actions.ts, mirroring the existing merge/approve pattern exactly.
  • Extends the accept-time isUnpinnedRatifyingAction guard (src/services/agent-approval-queue.ts) to also cover close, so a legacy unpinned row — staged before this fix deploys, or from a transiently-null stored head SHA — is refused and must be re-staged from a fresh sweep, rather than silently replayed.
  • Adds a live blacklist-membership re-check specifically for closeKind: "blacklist": the head-SHA pin alone cannot catch a maintainer removing the contributor from the blacklist between staging and accept, since the PR's head never moves in that scenario. Re-runs the exact same pure findBlacklistEntry check the planner uses (processors.ts), just against the CURRENT repo settings instead of the plan-time snapshot baked into the sticky pending row.

Scoping note: full re-verification of the non-CI heuristic-close reasoning (duplicate-of-open-PR, slop-score threshold) is intentionally out of scope for this PR. The executor's existing step-6 live-CI re-check (agent-action-executor.ts, #2128) already covers the CI half of a heuristic close, and this PR's head-SHA pin now gives it real force-push protection too — bringing heuristic close to the same fail-safe level merge/approve already had, without importing duplicate-detection/slop-scoring logic into the accept path.

Scope

  • The PR title follows type(scope): short summary Conventional Commit format, for example fix(api): restore profile access checks.
  • This PR is focused and does not mix unrelated backend, UI, MCP, docs, dependency, and deploy changes.
  • This follows CONTRIBUTING.md and does not reintroduce GitHub Pages, VitePress, site/, or CNAME.
  • I linked an issue, or this is small enough that the summary explains why an issue is not needed.

Validation

  • git diff --check
  • npm run actionlint
  • npm run typecheck
  • npm run test:coverage — new code is 100% line+branch covered in both changed files (verified against coverage/lcov.info: agent-approval-queue.ts 62/62 lines, 81/81 branches; the handful of uncovered branches remaining in agent-actions.ts are pre-existing, outside the lines this PR touches). Global 96.54%/95.52%.
  • npm run test:workers
  • npm run build:mcp
  • npm run test:mcp-pack
  • npm run ui:openapi:check
  • npm run ui:lint
  • npm run ui:typecheck
  • npm run ui:build
  • npm audit --audit-level=moderate — 0 vulnerabilities
  • New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries

Full suite: 5994/5994 passing (one pre-existing test needed its fixture updated — it staged a heuristic close with no head pin to test the close-precision breaker, which this fix now correctly rejects before reaching the breaker; added the pin so it still exercises the breaker logic it was written for). Also ran npm run db:migrations:check (unaffected, green).

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed.
  • Public GitHub text stays sanitized, low-noise, and does not imply compensation guarantees or optimization tactics.
  • Auth, cookie, CORS, GitHub App, Cloudflare, or session changes include negative-path tests. This touches the real-merge/close execution path — added regression tests for: unpinned close denied, force-pushed pinned close superseded, still-blacklisted close proceeds, no-longer-blacklisted close superseded.
  • API/OpenAPI/MCP behavior is updated and tested where needed. No OpenAPI/MCP surface changed.
  • UI changes — N/A, no UI changed.
  • Visible UI changes — N/A, no UI changed.
  • Public docs/changelogs are updated where needed; changelogs are only edited for release-prep PRs. No changelog edit.

Notes

  • Found via an adversarial audit of the review engine's actuation pipeline, specifically hunting for the sibling of a class of bug (#2377/#2422/#2262) already fixed for approve/merge but not extended to close.

@dosubot dosubot Bot added the size:M label Jul 2, 2026
@loopover-orb

loopover-orb Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Warning

🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨

⏸️ Gittensory review result - manual review recommended

Review updated: 2026-07-02 06:51:49 UTC

4 files · 1 AI reviewer · no blockers · readiness 68/100 · CI pending · blocked

⏸️ Suggested Action - Manual Review

  • Touches a guarded path — held for manual review

Review summary
The change correctly pins newly planned close actions to the reviewed head and extends accept-time rejection for legacy unpinned close rows, which closes the force-push freshness gap for staged closes. It also adds live rechecks for blacklist and linked-issue hard-rule close justifications before execution; the linked-issue path mirrors the existing merge-side revalidation closely enough to avoid replaying stale policy state. I do not see a reachable break in the provided post-change file content, but the new accept-time linked-issue block is dense and should be kept aligned with the planner/existing merge recheck conventions.

Nits — 7 non-blocking
  • nit: src/services/agent-approval-queue.ts:121 has an overlong narrative comment with PR-review meta text that should be shortened to the invariant being enforced, because this hot path is already difficult to scan.
  • nit: src/services/agent-approval-queue.ts:133 says a failed token mint falls back to a safe supersede path, but the test at test/unit/agent-approval-queue.test.ts demonstrates that the resolver can still return a violation with ciToken undefined; tighten the comment to match the actual fail-open/fail-soft contract.
  • nit: test/unit/agent-approval-queue.test.ts adds many long regression names and comments that restate implementation details; trim them so future failures point at the behavior rather than making the suite harder to navigate.
  • src/services/agent-approval-queue.ts:119 should factor the repeated repo-owner/author close-eligibility derivation into the same local helper used by the merge hard-rule recheck below, or at least keep the two blocks textually identical so future owner/automation policy changes do not drift.
  • src/services/agent-approval-queue.ts:136 should assert in tests that resolveLinkedIssueHardRule receives the current config from loadLinkedIssueHardRules for linked-issue close revalidation, since config drift is one of the main stale-state cases this PR is meant to close.
  • Readiness score is below the configured threshold — Use the readiness panel as advisory maintainer context; the score does not block this PR.
  • Touches a guarded path — held for manual review — A maintainer must review and merge this change.
Signal Result Evidence
Code review ✅ No blockers 1 reviewer
Linked issue ⚠️ Missing No linked issue or no-issue rationale found.
Related work ⚠️ 1 scoped overlap Top overlaps are listed below; lower-confidence bulk is hidden.
Change scope ❌ 8/20 High review scope from cached public metadata (size label size:L; no linked issue context).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 65 registered-repo PR(s), 55 merged, 548 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 65 PR(s), 548 issue(s).
Gate result ⚠️ Not blocking Advisory; not blocking this PR.
Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository registration is not available in the local Gittensory cache.
  • Public profile languages: not available
  • Official Gittensor activity: 65 PR(s), 548 issue(s).
  • Related work: Titles/paths share 7 meaningful terms. (PR #2478)
Contributor next steps
  • Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • Explain no-issue PR.
  • Review top overlaps.
  • Add a concise scope and risk note.
  • Triage stale or unlinked PRs.
  • No action.
  • Link the issue being solved, or explicitly explain why this is a no-issue PR.
  • Check active issues and PRs before submitting.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.

🟩 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.

  • Re-run Gittensory review

@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.96%. Comparing base (e30cd23) to head (aeb9cae).
⚠️ Report is 31 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2477      +/-   ##
==========================================
+ Coverage   95.93%   95.96%   +0.02%     
==========================================
  Files         225      226       +1     
  Lines       25336    25450     +114     
  Branches     9217     9258      +41     
==========================================
+ Hits        24306    24422     +116     
  Misses        417      417              
+ Partials      613      611       -2     
Files with missing lines Coverage Δ
src/services/agent-approval-queue.ts 100.00% <100.00%> (ø)
src/settings/agent-actions.ts 92.98% <100.00%> (+0.18%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

JSONbored added 2 commits July 1, 2026 23:36
…ify live state at accept time

Staged close actions (blacklist, linked-issue-hard-rule, heuristic)
never had expectedHeadSha set, unlike approve/merge. The accept-time
force-push guard only fires when a pin exists, so an unpinned close's
freshness check trivially compared the live head against itself
(both sides derive from the same fresh DB read) and could never catch
a force-push during the queue wait.

- Pin expectedHeadSha on all three close-construction sites, mirroring
  merge/approve.
- Extend the accept-time isUnpinnedRatifyingAction guard to cover
  close, so a legacy unpinned row (staged before this fix, or from a
  transiently-null stored head SHA) is refused rather than replayed.
- Add a live blacklist-membership re-check specifically for
  closeKind: "blacklist": the head-SHA pin alone doesn't catch a
  maintainer removing the contributor from the blacklist between
  staging and accept, since the head never moves. Re-runs the same
  pure findBlacklistEntry check the planner uses, against CURRENT
  settings instead of the plan-time snapshot baked into the sticky
  pending row.

Full re-verification of non-CI heuristic-close reasoning (duplicate-
of-open-PR, slop threshold) is intentionally out of scope here: the
executor's existing step-6 live-CI re-check already covers the CI
half for a heuristic close, and this PR's head-SHA pin now gives it
real force-push protection too, matching the same fail-safe level
merge/approve already had.

Fixes #2452
…time

The blacklist close re-check added for #2452 only covered closeKind
"blacklist". A closeKind "linked-issue-hard-rule" close staged while the
linked issue was ineligible would still fire after the issue became
eligible or the hard-rule config changed, as long as the PR head SHA
didn't move (flagged by the gate's own review of #2452).

Mirrors the blacklist re-check: re-resolve the hard rule against current
state at accept time and supersede the close if it's no longer violated.
Fails open on a token-mint failure, matching the existing merge-side
re-check's fail-open contract.
@JSONbored
JSONbored force-pushed the fix/pin-close-actions-head-sha-2452 branch from 7b84072 to 107e28c Compare July 2, 2026 06:36
@dosubot dosubot Bot added size:L and removed size:M labels Jul 2, 2026
…oses

The linked-issue hard-rule close re-check only re-validated rule
violation, not close-eligibility itself. settings.closeOwnerAuthors is a
live toggle that can flip to false between staging and accept without
moving the PR head SHA -- an owner PR staged for close while the setting
was true would still close after it was turned off (flagged by the
gate's own review, second pass on #2452).

Mirrors the existing merge-side closeEligible derivation: if the author
is no longer close-eligible, supersede without even consulting the hard
rule, same as the merge path's owner/automation exemption.
@JSONbored
JSONbored merged commit de8bf57 into main Jul 2, 2026
12 checks passed
@JSONbored
JSONbored deleted the fix/pin-close-actions-head-sha-2452 branch July 2, 2026 07:12
@github-project-automation github-project-automation Bot moved this from Todo to Done in gittensory - v1 roadmap Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier.

Projects

No open projects
Status: Done

Development

Successfully merging this pull request may close these issues.

fix(agent-actions): pin staged close actions to a head SHA and re-verify live state at accept time

1 participant