Allow safe author-lock recovery after the owning MCP session exits #753
Closed
opened 2026-07-18 18:20:34 -05:00 by jcwalker3
·
1 comment
No Branch/Tag Specified
master
fix/issue-757-create-issue-bootstrap-anti-stomp
fix/issue-745-reconciler-moot-lease-gate
fix/issue-749-create-issue-bootstrap
fix/issue-755-owning-pr-recovery
fix/issue-753-dead-pid-author-lock-recovery
fix/issue-751-checks-assessor
feat/issue-747-sliding-lease-ttl
fix/issue-741-canonical-root-consumers
fix/issue-742-merger-lease-provenance
feat/issue-739-canonical-root-consumption
feat/issue-706-canonical-repository-root
fix/issue-735-author-org-repo-forwarding
fix/issue-733-delete-branch-repo-forwarding
feat/issue-609-prepared-review-verdict-resume
fix/issue-702-stale-binding-lease-recovery
issue-723-role-poisoning
fix/issue-723-capability-role-invariants
fix/issue-720-expired-decision-lock
docs/mcp-stable-control-runtime-policy
feat/issue-687-reconciler-branch-delete
fix/issue-709-decision-lock-cross-profile
fix/issue-695-native-transport-quarantine
fix/issue-698-report-validator-schema
fix/issue-699-structured-auth-mcp-errors
fix/issue-695-native-quarantine-v2
fix/issue-691-obsolete-reviewer-lease-cleanup
fix/issue-683-workflow-guard-hardening
chore/issue-681-preserve-review-session-wip
fix/issue-671-block-stable-branch-push
fix/issue-675-residual-preflight-remediation
fix/issue-673-remediate-regressions-part2
fix/issue-673-remediate-regressions
feat/issue-603-lifecycle-labels
fix/issue-627-set-issue-labels-pagination
feat/issue-601-first-class-leases
feat/issue-612-incident-bridge
feat/issue-600-controller-allocator-api
fix/issue-620-head-scoped-review-locks
feat/issue-613-allocator-db-substrate
feat/issue-610-live-remote-parity
feat/issue-503-reviewer-active-worktree
feat/issue-470-preflight-contract
feat/issue-440-lock-recovery
feat/issue-440-branch-recovery
feat/issue-458-queue-fail-closed-copy
feat/issue-400-early-duplicate-work-gate
feat/issue-308-reconcile-inventory-pagination
feat/issue-308-reconcile-pagination-proof
docs/issue-261-agent-temp-artifact-cleanup
feat/issue-262-map-commit-files
fix/infra-stop-conflict-marker-false-positive
feat/issue-139-role-aware-task-routing
feat/issue-188-continuation-selection-wall
feat/issue-189-continuation-mode-proofs
feat/issue-232-refresh-wiki-proof-heads
feat/issue-210-block-workspace-edits
feat/issue-204-exact-issue-lock
docs/issue-80-label-taxonomy
docs/issue-79-safety-boundary-updates
v1.1.0
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
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#753
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.
Summary
A durable author issue lock becomes permanently unusable when the MCP session that created it exits, even though every piece of ownership evidence still matches exactly. There is no sanctioned native path to recover it, and the obvious re-lock path is blocked by a guard that a real PR branch can never satisfy.
Observed failure
Reproduced against PR #750 / issue #749 while attempting a routine branch update.
The durable lock record for issue #749 matches its work exactly:
749fix/issue-749-create-issue-bootstrap— the exact head branch of open PR #750branches/issue-749-create-issue-bootstrap— a real, registered Git worktreejcwalker3/ profileprgs-authore349839fd739e10af2df9c0f429ac487e8ca8b9dThe only thing that changed is that the recording MCP process exited:
session_pid/pid=48545, which is dead (kill -0fails, no such process)Consequences observed:
assess_lock_freshness(issue_lock_store.py) returnsstatus="stale",live=false, reasonowner pid 48545 is not alive. Note the lease itself had not expired —work_lease.expires_atwas still roughly 55 minutes in the future. The lock is non-live purely because of PID death.gitea_update_pr_branch_by_mergerequires a live author lock viaverify_lock_for_mutation, and correctly fails closed.gitea_lock_issuecannot recover the lock from the existing PR worktree.assess_issue_lock_worktree(issue_lock_worktree.py) rejects the worktree becausebase_equivalentisfalse, demanding that the worktree HEAD equal a base branch (master/main/dev) SHA before the lock may be taken.Point 3 is the structural part of the defect: an existing PR branch is necessarily ahead of its base, so it can never be base-equivalent. The branch above is 2 commits ahead of and 1 behind master. Any branch with work on it fails this check by construction, which makes normal re-lock unreachable after a session restart for every PR that already exists.
Why the existing reclaim path does not cover this
assess_expired_lock_reclaimalready sanctions reclaiming a non-live lock when the owner process is dead or the worktree is missing. However,assess_same_issue_lease_conflictonly consults it inside anis_lease_expired(...)branch. A lock that is stale-by-dead-PID but whose lease has not yet expired never reaches that path, so the existing recovery affordance does not apply to this case.Non-sanctioned escapes (explicitly rejected)
The following were considered and refused as recovery, because each launders false evidence through a guard:
stacked_base_branch/stacked_base_prto bypass base-equivalenceRequired behavior
Provide a native, truthful recovery path for a dead-session durable author lock when ownership evidence still matches exactly, without weakening the guards that protect brand-new issue claims.
Acceptance criteria
AC1 — Recovery may succeed only when all required evidence agrees:
AC2 — The recovery must:
AC3 — The recovery must remain fail-closed for:
AC4 — Scope discipline:
AC5 — Downstream acceptance: a recovered lock must satisfy
gitea_update_pr_branch_by_merge, existing PR mutation ownership verification, and subsequent author handoff and PR update paths.AC6 — Evidence: structured recovery evidence is recorded for auditing, without exposing secrets.
AC7 — Regression tests cover positive recovery and every rejection condition above, including: dead PID with exact proof succeeds; live prior PID refused; different author/profile refused; different branch refused; different worktree refused; dirty worktree refused; local head differing from remote refused; PR head differing refused; competing live lock or lease refused; multiple candidate branches refused; malformed or incomplete durable lock refused; new-issue base-equivalence still enforced; recovered lock satisfies update-by-merge ownership checks; recovery record preserves truthful provenance; no manual file seeding required.
References
assess_expired_lock_reclaim; its reclaim path is gated behind lease expiry and so does not reach the dead-PID-but-unexpired case.verify_lock_for_mutationownership re-check consuming lock freshness.[THREAD STATE LEDGER] Issue #753 — implementation pushed; PR #754 opened by author
What is true now:
3edeba4d7f0425bf9a43What changed:
3edeba4d7fand pushed the branchWhat is blocked:
Who/what acts next:
3edeba4d7fCanonical Issue State
STATE:
Issue #753 is open with an implementation pushed and PR #754 opened against master at head
3edeba4d7f. No review verdict exists.WHO_IS_NEXT:
reviewer
NEXT_ACTION:
Perform a fresh independent native review of PR #754 pinned at head
3edeba4d7f, checking each acceptance criterion AC1 through AC7 in this issue against the diff.NEXT_PROMPT:
WHAT_HAPPENED:
The author session verified the defect against the live durable lock for issue #749: the lock matched issue #749, branch fix/issue-749-create-issue-bootstrap, and the registered worktree branches/issue-749-create-issue-bootstrap, with a clean worktree and local head, remote head and PR #750 head all
e349839fd7, while recorded pid 48545 was dead and the lease still had roughly 55 minutes remaining. Issue #753 was then created, its lock acquired on a fresh worktree at master, the recovery assessor implemented with regression tests, and PR #754 opened.WHY:
A durable author lock records the owning MCP session PID. When that process exits the lock is classified stale even inside its lease TTL, so ownership checks fail closed, and re-locking is unreachable because the worktree gate requires the branch to match its base while any branch carrying work is ahead of its base. This change supplies a native recovery path gated on exact durable ownership evidence.
RELATED_PRS:
PR #754 implements this issue. PR #750 and issue #749 supplied the reproduction and were not modified. PR #746 was not modified.
BLOCKERS:
no blocker
VALIDATION:
New suite tests/test_issue_753_dead_pid_lock_recovery.py 33 passed. Issue-lock, adoption, store, provenance, registration, duplicate-gate, worktree and create-issue-guard suites 120 passed. MCP server, commit payloads, handoff ledger, PR ownership and branch cleanup suites 286 passed. Full suite 3498 passed, 6 skipped, 2 failed. The 2 failures are test_issue_702_review_findings_f1_f6.py::TestF1RecoveryBeforeTerminalProbe::test_removed_worktree_recovers_before_probe and test_reconciler_supersession_close.py::TestReconcilerSupersessionMcpTool::test_tool_posts_comment_and_closes_superseded_pr_issue, both reproduced identically in a throwaway detached worktree at pristine master
0425bf9a43, so they are pre-existing. git diff --check clean.LAST_UPDATED_BY:
author