fix(reconciler): PR-scoped post-merge cleanup executor + expired reviewer-lease reclaim (Closes #855) #866
Open
jcwalker3
wants to merge 3 commits from
fix/issue-855-pr-scoped-merged-cleanup into master
pull from: fix/issue-855-pr-scoped-merged-cleanup
merge into: :master
:master
:fix/issue-871-durable-lock-head-refresh
:feat/issue-658-mcp-restart-coordinator
:feat/issue-639-webui-system-health-dashboard
:feat/issue-642-sanctioned-restart-controls
:fix/issue-855-pr-scoped-merged-cleanup
:feat/issue-657-mcp-restart-path-inventory-guard
:docs/issue-656-mcp-restart-governance
:fix/issue-850-native-mcp-bootstrap
:fix/issue-842-conflict-fix-lease-lifecycle
:feat/issue-628-autonomous-handoffs-orchestration
:fix/issue-860-dirty-orphan-worktree-recovery
:fix/issue-868-dirty-rebind-inventory-journal
:fix/issue-858-audit-merged-pr-aware
:fix/issue-790-slice-a-heartbeat-policy
:feat/issue-637-timeline-model
:feat/issue-646-policy-guardrail-visibility
:feat/issue-638-webui-app-shell-phase1
:fix/issue-851-cleanup-worktree-before-remote-delete
:fix/issue-843-cross-role-allocation-handoff
:fix/issue-844-exclude-epic-containers
:feat/issue-634-readonly-system-health-api
:fix/issue-840-cross-role-queue-allocation
:feat/issue-822-atomic-install-authority-kernel
:feat/issue-633-console-authz-audit-model
:feat/issue-636-inventory-api
:fix/issue-815-preflight-worktree-forwarding
:feat/issue-812-publish-unpublished-commit
:feat/issue-635-project-registry-api
:feat/issue-798-worker-registry-schema
:feat/issue-610-live-remote-parity
:docs/issue-632-web-console-architecture
:fix/issue-760-exact-owner-renewal
:fix/issue-787-kill-segment-separators
:chore/issue-681-preserve-review-session-wip
Labels
Clear labels
allocator
anti-stomp
architecture
bug
chore
codex
concurrency
contamination
control-plane
dashboard
database
design
documentation
enhancement
gitea
glitchtip
important
incident
incident-bridge
integration
jenkins
labels
leases
mcp
mcp-health
mcp-menu
multi-project
mutating
nice-to-have
observability
portability
preflight
protected-branch
queue
read-only
reconnect
recovery
refactor
release
reliability
resumable-review
reviewer
roadmap
safety
security
self-hosted
sentry
stale-runtime
status:blocked
status:in-progress
status:pr-open
status:ready
terminal-lock
testing
tracker
type:bug
type:feature
type:feature
type:guardrail
visibility
workflow
workflow-hardening
workflow-hardening
Controller-owned work allocator
Prevent concurrent LLM session stomping
Architecture / structural design
OpenAI Codex client / workflow session surface
Concurrent session safety
Workflow or session contamination incident
MCP control-plane coordination and allocation authority
MCP operational dashboard/queue view
Internal coordination storage (SQLite/Postgres)
Design / investigation, no implementation
Docs / runbooks
New feature or improvement
Gitea MCP workflow
GlitchTip integration
Operational or process incident requiring durable audit trail
Sentry-to-Gitea incident bridging
Integration testing
Jenkins integration
Label taxonomy management
Lease adopt/release/expire lifecycle
MCP server / tooling
MCP namespace and runtime health
MCP menu surface
Work spanning multiple monitoring projects or Gitea repos
Mutating action; requires gating
Observability, metrics, traces, error reporting
Cross-platform / portability
Shared preflight gates before mutation
Protected branch / stable-branch policy concern
Work queue visibility and allocation
Read-only, no mutation
MCP client reconnect/reload recovery path
Recovery paths for stale/foreign leases
Code refactor / restructure
Release / versioning
Reliability / failure handling
Persist and resume prepared review verdicts across sessions
Reviewer workflow tooling
Roadmap / umbrella issue
Safety rails and fail-closed mutation guards
Security / trust boundary
Self-hosted infrastructure integration
Sentry error monitoring integration
Stale backend daemon / runtime-vs-master parity failures
Issue is blocked
Issue is being worked on
Issue has an open pull request
Issue is ready for work
Terminal review lock (#332) path
Tests / test coverage
Issue tracker hygiene / meta
Bug or defect
Feature or enhancement
Feature or enhancement
Safety gate or guardrail
Workflow state visibility for LLMs/operators
Cross-tool workflow
LLM workflow coordination hardening
LLM workflow coordination hardening
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: Scaled-Tech-Consulting/Gitea-Tools#866
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #855
Adds the PR-scoped post-merge cleanup path a reconciler needs to finish exactly one merged PR without a batch sweep over other PRs, plus the explicit expired-reviewer-lease reclaim the batch path never had.
PR-scoped selector
gitea_reconcile_merged_cleanupsgains an optionalpr_number. When set, only that merged PR is assessed and acted on:[pr_number], failing closed on any drift.The existing execute loop then acts on the single pinned entry only — remove the safe worktree, reassess ownership, then delete the remote branch — touching no other PR. Batch behaviour is preserved when
pr_numberis omitted.Expired reviewer-lease reclaim (AC4)
An expired or stale reviewer lease no longer protects an already-merged branch forever.
branch_cleanup_guard.assess_expired_reviewer_lease_reclaimdecides reclaim explicitly and fail-closed: only a reviewer lease, only expired/stale status, only a proven-merged PR, only a proven-dead owner process, and only when no competing active claimant uses the branch._collect_branch_ownership_recordsgathers that evidence from authoritative state (live PR merged-state, lease owner liveness, and the full ownership inventory for competing-claimant detection) and evaluates it after the complete inventory is built — so the post-worktree-removal reassessment is what clears the branch delete. Any unknown fails closed. Author/merger/controller/reconciler leases are untouched; active leases, worktree bindings, issue locks, and live sessions still block.Acceptance criteria
pr_numberselector + live resolution[pr_number]; scratch filtered_ownership_records_for_branchpost-removal (#852)assess_expired_reviewer_lease_reclaim+ collector wiringselection_mode,selected_pr_number, entries, actionsScope boundary
This is the reconciler-executor gap. The audit-side merged-PR linkage was a distinct fix and already landed as #859; this PR adds no audit change. It is additive to the batch path (unchanged) and weakens no ownership guard beyond the single, fully-gated expired-reviewer-lease reclaim.
Tests
tests/test_issue_855_expired_reviewer_reclaim.py(new): fail-closed matrix for the reclaim decision plus collector wiring — merged+dead+uncontested reclaims; not-merged, live-owner, competing-worktree, and author-lease cases stay protective.tests/test_branch_cleanup_guard.py: exact-PR selector coverage — ignores newer PRs in the batch queue, execute mutates only the selected PR, unknown/not-merged/invalid fail closed, batch mode preserved.Evidence, run from the registered worktree at head
24c52ab:pytest tests/test_issue_855_expired_reviewer_reclaim.py— 16 passed, 5 subtests.pytest tests/test_branch_cleanup_guard.py -k TestIssue855ExactPrSelector— 6 passed.test_branch_cleanup_guardfailures andtest_reconciler_supersession_close, all three reproducing identically on master6d0015caand untouched by this change.Provenance
Implemented as
jcwalker3/prgs-authoronScaled-Tech-Consulting/Gitea-Tools, master parityin_parity=truemutation_safe=trueat6d0015ca. A prior blocked cycle's partial selector work-in-progress on this branch (dead session pid 18052) was adopted, fast-forwarded onto current master, validated, and completed with the missing AC4 reclaim. The dead-session issue-#855 lock was recovered through the sanctioned unpublished-claim path (recorded pid dead; branch, worktree, and claimant matched; local head strictly descends from recorded base6d0015ca). The head was published viagitea_publish_unpublished_issue_branchwith read-after-write verification. No base merge; base stays at master.Canonical PR State
STATE: awaiting-review
WHO_IS_NEXT: reviewer
BLOCKED_ROLE: none
NEXT_ACTION: Review this PR at head
24c52abagainst issue #855 acceptance criteria 1-9; verify the pr_number selector fails closed on invalid/ambiguous/not-merged and pins the entry set to the single PR, and that the expired-reviewer-lease reclaim is fully gated (reviewer role, expired/stale, PR proven merged, owner proven dead, no competing active claimant); confirm the pre-existing baseline failures match master 6d0015ca; submit verdict; stopNEXT_PROMPT: Review this PR for issue #855 as prgs-reviewer pinned to head
24c52ab. Verify the PR-scoped selector (fail-closed on invalid/non-positive/unresolvable/ambiguous/not-merged, scratch filtered, entry set pinned to the one PR) and the expired-reviewer-lease reclaim (fully gated + fail-closed, evaluated after full ownership inventory, unblocking only the post-removal remote delete). Run pytest tests/test_issue_855_expired_reviewer_reclaim.py and pytest tests/test_branch_cleanup_guard.py, confirm the pre-existing baseline failures match master6d0015ca, submit a verdict, and stop.Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #866
issue: #855
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 26133-df7e376b1b5c
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-866
phase: claimed
candidate_head:
24c52abf6btarget_branch: master
target_branch_sha:
ef14622ba0last_activity: 2026-07-24T05:12:11Z
expires_at: 2026-07-24T05:22:11Z
blocker: none
Canonical PR State
STATE: reviewed-approved
WHO_IS_NEXT: merger
NEXT_ACTION: Route PR #866 to a fresh gitea-merger pinned to head
24c52abf6b. Merger syncs base (2 behind; branch protection requires current base), obtains a fresh reviewer approval at the resulting head, then merges. No cleanup here — defer branch/worktree reclaim to reconciler.NEXT_PROMPT:
WHAT_HAPPENED: Independent security-focused review of the full production diff (branch_cleanup_guard.py +84, gitea_mcp_server.py +224/-11) plus tests. Verified all 9 acceptance criteria; reproduced the reclaim-guard fail-closed matrix and the PR-scoped selector; independently ran the focused tests (16 pass + 5 subtests reclaim; 6/6 selector) and a 214-pass neighbour sweep; reproduced all claimed baseline failures identically on clean master
ef14622.WHY: assess_expired_reviewer_lease_reclaim is strictly fail-closed (reclaim only when role==reviewer AND status in {expired,stale} AND pr_merged is True AND owner_pid_alive is False AND competing_active_claimant is False; any None/contrary keeps the lease protective — TTL alone never authorizes). Collector wiring supplies pr_number/auth/base_api on BOTH real call sites (cleanup_merged_pr_branch ~L10586, execute-loop _ownership_records_for_branch ~L11463), not only in tests; the reclaim pass runs after the complete ownership inventory so competing claimants cannot be missed by iteration order. Selector fails closed on invalid/non-positive/unresolvable/ambiguous/not-merged and pins the entry set to [pr_number]; worktree removal precedes remote delete with fresh post-removal ownership reassessment and read-after-write delete verification; batch behaviour and role authority (reconciler-gated execution) preserved. Zero PR-introduced regressions.
ISSUE: 855
HEAD_SHA:
24c52abf6bREVIEW_STATUS: APPROVE
MERGE_READY: no — approval is head-scoped; PR is 2 commits behind master and branch protection requires a current base, so a base sync + fresh review at the new head are required before merge.
BLOCKERS: none blocking approval of the reviewed head. Merge-time prerequisite: update_pr_branch_by_merge + fresh review after sync.
VALIDATION: tests/test_issue_855_expired_reviewer_reclaim.py 16 passed + 5 subtests @ 24c52ab; tests/test_branch_cleanup_guard.py -k TestIssue855ExactPrSelector 6 passed @ 24c52ab; neighbour sweep 214 passed @ 24c52ab; baseline failures test_authorized_cleanup_deletes_through_api_path, test_active_author_ownership_blocks_before_delete, test_reconciler_supersession_close reproduced identically on clean master
ef14622(pre-existing, not regressions); one moot-lease test failure is environment-dependent (branches/merge-pr487 layout) and also fails identically on master.NATIVE_REVIEW_PROOF: Verdict rendered via the native gitea_submit_pr_review APPROVE mutation by sysadmin / prgs-reviewer at head
24c52abf6b(reviewer session 26133-df7e376b1b5c, reviewer-lease comment 15387, workflow_hash 263d0a6cb8a6). Not a hand-authored approval claim.LAST_UPDATED_BY: sysadmin / prgs-reviewer (reviewer)
Co-Authored-By: Claude Opus 4.8 (1M context) [email protected]
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #866
issue: #855
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 26133-df7e376b1b5c
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-866
phase: released
candidate_head:
24c52abf6btarget_branch: master
target_branch_sha:
ef14622ba0last_activity: 2026-07-24T05:16:15Z
expires_at: 2026-07-24T05:26:15Z
blocker: manual-release
Canonical Issue State
STATE: awaiting-review
WHO_IS_NEXT: reviewer
NEXT_ACTION: Perform a fresh independent review of PR #866 at head
499b87c48218cf03a3b1f34a5d8a17549d820564against issue #855 acceptance criteria 1-9. Do NOT reuse review #534 (invalid at the new head). Verify thepr_numberselector fail-closed behavior (invalid/non-positive/unresolvable/ambiguous/not-merged, scratch filtered, entry set pinned to the one PR) and the fully-gated expired-reviewer-lease reclaim (reviewer role, expired/stale, PR proven merged, owner proven dead, no competing active claimant; evaluated after full ownership inventory). Run the two focused suites, confirm baseline failures match masteref14622, submit a verdict, and stop.NEXT_PROMPT:
WHAT_HAPPENED: Author base-sync only. Branch
fix/issue-855-pr-scoped-merged-cleanupwas synced to currentmastervia the sanctioned authorgitea_update_pr_branch_by_mergepath (style=merge; no rebase, no force-push). Former PR head24c52abf6b80e7ed562294371eee9fe8620b686e-> new PR head499b87c48218cf03a3b1f34a5d8a17549d820564; basemastermerged atef14622ba0badd0cd7d654c05af13aca7d81bc05. Change scope is the base-merge commit only — no production file edits beyond the merge. Review #534 (APPROVED @24c52ab) is invalidated by the head change; reviewer/merger leases and any prepared verdict scoped to the former head are superseded.WHY: PR #866 was approved at head
24c52abbut branch protection requires the PR branch to contain the current base, and the branch was 2 commits behind protectedmaster(ef14622). Gitea could merge base in without conflicts, so the sanctioned author update-by-merge was the required operation to make it merge-eligible; a new head necessarily invalidates the prior approval and requires fresh independent review.RELATED_PRS: #865 (merged @
ef14622, advanced base); #859 (merged; audit-side merged-PR linkage — distinct, already landed); #818/#638 (retained reproduction fixtures, untouched).BLOCKERS: none.
commits_behind=0,mergeable=true,has_conflicts=false, PR open,mutation_safe=true,in_parity=true. No foreign live lock/lease. Only gate for merge now is the required fresh review at the new head.VALIDATION:
gitea_update_pr_branch_by_merge-> success, performed=true, style=merge, force_push=false, rebase=false. Post-syncgitea_assess_pr_sync_status: pr_head=499b87c48218cf03a3b1f34a5d8a17549d820564, base_head=ef14622ba0badd0cd7d654c05af13aca7d81bc05, commits_behind=0, mergeable=true, has_conflicts=false, approval_at_current_head=false, recommended_next_action=fresh_review_required, checks_status=not_required. Master parity in_parity=true, mutation_safe=true atef14622. No focused #855 suite re-run: clean base merge, no production-code delta beyond the merge commit (step-11 trigger did not apply).LAST_UPDATED_BY: jcwalker3 / prgs-author
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #866
issue: #855
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 51916-0f368a202092
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-866
phase: claimed
candidate_head:
499b87c482target_branch: master
target_branch_sha: none
last_activity: 2026-07-24T06:07:13Z
expires_at: 2026-07-24T06:17:13Z
blocker: none
Canonical PR State
STATE: changes-requested
WHO_IS_NEXT: author
BLOCKED_ROLE: none
NEXT_ACTION: As prgs-author on issue #855, run gitea_update_pr_branch_by_merge on PR #866 to merge current live master (
89657a06c4) into fix/issue-855-pr-scoped-merged-cleanup, producing a new head that incorporates89657a06. Then route the new head to a fresh gitea-reviewer for a head-scoped review. No code changes are required — the implementation is sound; only the stale base blocks approval.NEXT_PROMPT:
WHAT_HAPPENED: Independent security-focused review of the complete current diff at head
499b87c(branch_cleanup_guard.py +84, gitea_mcp_server.py +224/-11, two test files). Two-parent containment independently proven:499b87cdescends from original head24c52abAND incorporatesef14622(merge-base is exactlyef14622). It does NOT incorporate current live master89657a06.WHY: BLOCKING — the reviewed head is NOT mergeable at a current base. Authoritative sync assessment: commits_behind=2, branch_protection_requires_current_base=true, approval_valid_for_merge=false, stale_approval=true, recommended_next_action=update_branch_by_merge. Live master advanced
ef14622->89657a06(PR #869/#868 merged) AFTER this branch synchronized toef14622, so the branch is stale again. Approving a behind head would let a merge land on a non-current base in violation of branch protection. This is a synchronization requirement, not a code defect: the implementation is fail-closed and correct.CODE_ASSESSMENT (no changes required):
ISSUE: 855
HEAD_SHA:
499b87c482REVIEW_STATUS: REQUEST_CHANGES
MERGE_READY: no — head is 2 commits behind live master
89657a06c4and branch protection requires a current base.BLOCKERS: Stale base. Required change: update_pr_branch_by_merge onto current master
89657a06c4, then fresh head-scoped review. No source edits required.VALIDATION: Containment verified via git merge-base --is-ancestor (descends
24c52ab: yes; incorporatesef14622: yes; incorporates89657a06: no; commits_behind=2). Sync state via gitea_assess_pr_sync_status (mergeable=true, has_conflicts=false, commits_behind=2, branch_protection_requires_current_base=true). Focused #855 tests and baseline-failure reproduction deferred to the post-sync reviewer, since the required base-sync produces a fresh head that supersedes this one; the feature diff is unchanged by a base merge.RELATED_PRS: #859 (audit-side merged-PR linkage, already merged — distinct scope). Reproduction fixture PR #818 / issue #638 must remain untouched.
NATIVE_REVIEW_PROOF: Verdict rendered via the native gitea_submit_pr_review REQUEST_CHANGES mutation by sysadmin / prgs-reviewer at head
499b87c482(reviewer session 51916-0f368a202092, reviewer-lease comment 15460, workflow_hash 263d0a6cb8a6). Not a hand-authored claim.LAST_UPDATED_BY: sysadmin / prgs-reviewer (reviewer)
Co-Authored-By: Claude Opus 4.8 (1M context) [email protected]
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #866
issue: #855
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 51916-0f368a202092
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-866
phase: released
candidate_head:
499b87c482target_branch: master
target_branch_sha: none
last_activity: 2026-07-24T06:09:14Z
expires_at: 2026-07-24T06:19:14Z
blocker: manual-release
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.