Base: 4e6f9ac32c9ba8f16be41845df9cd880199d59d8 Head: 0f97f2565890fc5015cf9cc035532809caec56cb
A pull request was a one-way object over MCP: it could be opened, reviewed and merged, but never closed without merging, never edited, and never discussed outside a review body. Integrating the hermes-agent backlog left 24 stale PRs that no tool could close and a description that went stale mid-flight with no way to correct it.
New MCP tools
akurai_pr_close, akurai_pr_reopen, akurai_pr_update, akurai_pr_comment, akurai_pr_comments, akurai_branch_delete. All registered as mutating for audit/consent. db::close_pull_request already existed but had never been wired to any caller.
Comments deliberately carry no verdict and no head binding, so a machine identity may comment while remaining unable to approve — the asymmetry that makes "superseded by #29" sayable without weakening the review gate. Edits touch only title and body, never head_sha/base_sha/state, so a description fix can neither launder nor invalidate an approval.
New provenance on akurai_pr_get
head_reachable— a force-push, branch delete or re-sync can leave a PR pointing at an object the mirror no longer has. Nothing surfaced that, so the PR read as normal until a merge attempt failed on it. This is exactly how hermes-agent PR #22 became unmergeable while looking fine.base_current/base_head_sha—base_shais captured once at creation; when the default branch later moves, older PRs keep reporting a base that is no longer the tip, which is what makes their hosted diffs enormous and unreviewable.
akurai_branch_delete refuses the default branch and any branch that is the source of an open PR, so deleting a branch can never orphan a PR head.
Migration 014 also fixes a uniqueness defect
UNIQUE(repository_id, source_ref, target_ref, state) permitted only ONE closed PR per branch pair — the same shape of bug migration 013 fixed for merge_queue_entries. Republishing a branch after an abandoned attempt (v4..v9 of one fix) left the earlier PRs permanently unclosable. Replaced with a partial unique index over the active states (open, queued); terminal states now repeat freely. Without this, closing the backlog fails halfway.
Deliberately NOT changed
The native-executor branch gate. Feature branches are held to the untrusted-docker pinned-image path on purpose, so untrusted branch code never gets native host access — this repository's own verify-pr job is the proof that branch CI already works that way. Only the refusal message changed, to say so instead of reading as a flat capability gap.
Evidence
cargo test --lib → 333 passed, 0 failed. cargo clippy --all-targets clean, cargo fmt --check clean. New tests cover closed-duplicate branch pairs, merged PRs refusing close/reopen, comment round-trip and blank-body rejection, edits preserving approval, and the machine-identity comment/review asymmetry.
Exercised against the running service after a direct restart: 25 stale hermes-agent PRs closed with reasons (now 0 open), 10 dead branches deleted (26 → 16), and base_current: false correctly flagged PR #21's stale base.
Delivery note
An earlier akurai_repo_sync fast-forwarded hosted main straight to this commit, because this repository had protect_default_branch: false — the CI controller's own repository was not enforcing its own merge queue. No run had consumed it, so main was reset to 4e6f9ac and default-branch protection is now enabled. This PR is the re-delivery through the intended gate chain.
🤖 Generated with Claude Code
https://claude.ai/code/session_01FRtUmfFA7yWCFpX9dCaXvz