fix(author): bootstrap recovery for dirty orphaned issue worktrees #860

Open
opened 2026-07-23 20:25:22 -05:00 by jcwalker3 · 1 comment
Owner

Summary

This issue is a narrow bootstrap recovery primitive required to unblock the self-hosting deadlock that currently freezes Issue #850 / PR #853 and, subsequently, Issue #855.

It does not replace the broader native-bootstrap work in #850. It only provides a sanctioned recovery path for a same-claimant author session that owns a dirty registered worktree under a malformed PID-less durable lock, and needs to re-bind and synchronize with a newer remote PR head without discarding unpublished dirty bytes.

Prerequisite relationship

  • Completing #850 / PR #853 is blocked: the registered worktree is dirty with #528 remediation files, behind the remote PR tip, and the durable lock is same-claimant but PID-less/malformed. Existing recovery and renewal routes require a clean worktree and/or PID liveness evidence.
  • #855 demonstrates the same blocked recovery class (dirty registered worktree under same-claimant ownership with no sanctioned dirty-preserving rebind/sync). #855 must remain read-only while this bootstrap lands.
  • This issue is a prerequisite for finishing those threads; it is not a re-implementation of #850 native bootstrap.

Observed evidence (do not mutate #850 / #855)

PR #853 / Issue #850

Fact Value
PR head e9f6d68bd7271c8168079a32707353ab76ff3a89
Unresolved review #528 (REQUEST_CHANGES, stale relative to current head)
Registered worktree branches/issue-850-native-mcp-bootstrap
Local HEAD 3b2b4e1dcae945377443f801ef964b433de9cd22 (behind remote tip)
Dirty preserved files author_issue_bootstrap.py, author_mutation_worktree.py, tests/test_author_issue_bootstrap.py, gitea_mcp_server.py
Durable lock claimant jcwalker3 / prgs-author
PID / session PID absent
Expiry / heartbeat absent
Incorrect classification PID-less lock treated as live (is_lease_live=true class of bug)

Issue #855

Fact Value
Worktree branches/fix-issue-855-pr-scoped-merged-cleanup
Dirty yes (gitea_mcp_server.py, tests/test_branch_cleanup_guard.py)
Lock claimant jcwalker3 / prgs-author
Status blocked by the same recovery class; read-only for this work

Live master pin at filing

9301739910df7b51ca3abcdbf46cce25cfb026e5 (author runtime in parity, mutation_safe=true, live_stale=false, restart_required=false, stop_required=false).

Why existing recovery is insufficient

  • #753 / #768 / #772 (closed via PR #774) — dead-session recovery for clean worktrees. AC4 explicitly refuses dirty worktrees. #772 also refuses PID-less malformed locks (session_pid/pid required).
  • Exact-owner lease renewal — requires published heads and clean ownership evidence; does not preserve dirty patches while syncing to a newer remote PR head.
  • #850 native bootstrap — creates first worktrees for newly allocated issues; does not recover dirty orphaned claims. Its own PR #853 is the stuck evidence case.
  • No sanctioned operation currently establishes a live author binding while preserving dirty work, nor a dirty-preserving synchronization route against a newer remote PR head.

Duplicate search

Open issues inspected (including #850, #855, #858, #816, #790 family, #716, #713, #681). Closed recovery issues #753, #768, #772 / PR #774 examined.

No open or closed item provides all of:

  1. recovery from a dead or provably orphaned same-claimant lock;
  2. safe handling of malformed locks lacking PID data;
  3. preservation of dirty registered-worktree contents;
  4. canonical live author-session binding;
  5. safe synchronization with a newer remote PR head;
  6. conflict detection when upstream changed a dirty path.

Required capability

Add an explicit recovery operation (do not silently weaken gitea_lock_issue).

Caller-provided authoritative pins (verified, not trusted alone)

  • repository, issue, branch, registered source worktree
  • claimant identity and profile
  • expected local head
  • expected remote/PR head
  • expected dirty paths and content fingerprints

Eligibility (all required)

  • same claimant identity and profile
  • exact issue/repository/branch/worktree agreement
  • registered worktree under the canonical branches root
  • no active original process
  • no active competing author session or workflow lease
  • sufficient corroborating evidence when PID fields are absent
  • explicit caller-provided head and fingerprint pins
  • no foreign, duplicate, or ambiguous ownership
  • A PID-less lock must never be considered live merely because expiration fields are absent

Dirty preservation and sync

  • preserve original dirty file bytes and fingerprints
  • never clean, reset, overwrite, or delete the source worktree
  • create authoritative crash-journal state before filesystem or ownership mutation
  • synchronize against the pinned remote PR head without silently overwriting upstream changes
  • identify path-level conflicts when both upstream and the preserved patch changed a file
  • leave governed conflict-resolution state ordinary author editing can resolve
  • establish canonical author-session provenance only after recovery state is internally consistent
  • allow commit/publication preflights only after conflicts and recovery verification pass
  • prefer a separately prepared recovery worktree (or equivalent transactional design) that freezes the original source until the new binding is verified

Fail closed for

  • active original owner; foreign claimant; live/ambiguous competing session
  • mismatched repository, issue, branch, worktree, or heads
  • changed dirty fingerprints; unregistered/noncanonical paths
  • symlink or traversal escapes; unsafe lock-file symlinks
  • corrupt, partial, ownerless, or replayed journals
  • unexpected remote-head movement; unrelated conflicts

Crash-safety / idempotence

Test interruption: before journal persistence; after journal before recovery-worktree creation; after recovery-worktree before binding; after binding before completion; during retry. Retry must resume or fail safely without stealing ownership, duplicating worktrees, or losing dirty bytes.

Acceptance criteria

  1. Explicit recovery MCP operation accepts and verifies the authoritative expected values listed above.
  2. Eligibility requires all gates in this issue; PID-less locks are never live by missing expiry alone.
  3. Dirty bytes/fingerprints preserved; source worktree never cleaned/reset/deleted.
  4. Crash journal precedes filesystem/ownership mutation; retries are idempotent.
  5. Sync to pinned remote PR head with path-level conflict detection for overlapping dirty paths.
  6. Canonical author-session binding only after consistent recovery state; commit/publication preflights recognize recovered provenance.
  7. Fail closed for all cases listed above (active/foreign owners, mismatches, symlink attacks, journal corruption, remote drift).
  8. Synthetic regression coverage modeled on #850 and #855 without mutating their real state, including: dead same-claimant; PID-less malformed lock; expired corroborating workflow lease; dirty registered worktree; older local / newer remote PR head; overlapping upstream change; byte-for-byte dirty preservation; session binding; preflight recognition; active/foreign refusal; fingerprint/head/path mismatches; symlink attacks; crash/retry idempotence; original source remains recoverable.
  9. Focused + relevant full suites green (or baseline-proven pre-existing only against clean live master).
  10. Single PR closes this issue only; #850, PR #853, #855, their worktrees, locks, fingerprints, and PR heads remain unchanged.

Non-goals

Canonical next step

STATE: ready-for-author
WHO_IS_NEXT: author
NEXT_ACTION: Allocate a clean registered worktree from live master, implement the recovery operation + tests, publish one PR, hand off to gitea-reviewer
## Summary This issue is a **narrow bootstrap recovery primitive** required to unblock the self-hosting deadlock that currently freezes Issue #850 / PR #853 and, subsequently, Issue #855. It does **not** replace the broader native-bootstrap work in #850. It only provides a sanctioned recovery path for a same-claimant author session that owns a **dirty** registered worktree under a **malformed PID-less durable lock**, and needs to re-bind and synchronize with a newer remote PR head without discarding unpublished dirty bytes. ## Prerequisite relationship - Completing #850 / PR #853 is blocked: the registered worktree is dirty with #528 remediation files, behind the remote PR tip, and the durable lock is same-claimant but PID-less/malformed. Existing recovery and renewal routes require a clean worktree and/or PID liveness evidence. - #855 demonstrates the same blocked recovery class (dirty registered worktree under same-claimant ownership with no sanctioned dirty-preserving rebind/sync). **#855 must remain read-only** while this bootstrap lands. - This issue is a prerequisite for finishing those threads; it is not a re-implementation of #850 native bootstrap. ## Observed evidence (do not mutate #850 / #855) ### PR #853 / Issue #850 | Fact | Value | |---|---| | PR head | `e9f6d68bd7271c8168079a32707353ab76ff3a89` | | Unresolved review | #528 (`REQUEST_CHANGES`, stale relative to current head) | | Registered worktree | `branches/issue-850-native-mcp-bootstrap` | | Local HEAD | `3b2b4e1dcae945377443f801ef964b433de9cd22` (behind remote tip) | | Dirty preserved files | `author_issue_bootstrap.py`, `author_mutation_worktree.py`, `tests/test_author_issue_bootstrap.py`, `gitea_mcp_server.py` | | Durable lock claimant | `jcwalker3` / `prgs-author` | | PID / session PID | **absent** | | Expiry / heartbeat | **absent** | | Incorrect classification | PID-less lock treated as live (`is_lease_live=true` class of bug) | ### Issue #855 | Fact | Value | |---|---| | Worktree | `branches/fix-issue-855-pr-scoped-merged-cleanup` | | Dirty | yes (`gitea_mcp_server.py`, `tests/test_branch_cleanup_guard.py`) | | Lock claimant | `jcwalker3` / `prgs-author` | | Status | blocked by the same recovery class; **read-only for this work** | ### Live master pin at filing `9301739910df7b51ca3abcdbf46cce25cfb026e5` (author runtime in parity, `mutation_safe=true`, `live_stale=false`, `restart_required=false`, `stop_required=false`). ## Why existing recovery is insufficient - **#753 / #768 / #772 (closed via PR #774)** — dead-session recovery for **clean** worktrees. AC4 explicitly refuses dirty worktrees. #772 also refuses PID-less malformed locks (`session_pid/pid` required). - **Exact-owner lease renewal** — requires published heads and clean ownership evidence; does not preserve dirty patches while syncing to a newer remote PR head. - **#850 native bootstrap** — creates first worktrees for newly allocated issues; does not recover dirty orphaned claims. Its own PR #853 is the stuck evidence case. - No sanctioned operation currently establishes a **live author binding while preserving dirty work**, nor a **dirty-preserving synchronization** route against a newer remote PR head. ## Duplicate search Open issues inspected (including #850, #855, #858, #816, #790 family, #716, #713, #681). Closed recovery issues #753, #768, #772 / PR #774 examined. **No open or closed item provides all of:** 1. recovery from a dead or provably orphaned same-claimant lock; 2. safe handling of malformed locks lacking PID data; 3. preservation of dirty registered-worktree contents; 4. canonical live author-session binding; 5. safe synchronization with a newer remote PR head; 6. conflict detection when upstream changed a dirty path. ## Required capability Add an **explicit recovery operation** (do **not** silently weaken `gitea_lock_issue`). ### Caller-provided authoritative pins (verified, not trusted alone) - repository, issue, branch, registered source worktree - claimant identity and profile - expected local head - expected remote/PR head - expected dirty paths and content fingerprints ### Eligibility (all required) - same claimant identity and profile - exact issue/repository/branch/worktree agreement - registered worktree under the canonical branches root - no active original process - no active competing author session or workflow lease - sufficient corroborating evidence when PID fields are absent - explicit caller-provided head and fingerprint pins - no foreign, duplicate, or ambiguous ownership - **A PID-less lock must never be considered live merely because expiration fields are absent** ### Dirty preservation and sync - preserve original dirty file bytes and fingerprints - never clean, reset, overwrite, or delete the source worktree - create authoritative crash-journal state before filesystem or ownership mutation - synchronize against the pinned remote PR head without silently overwriting upstream changes - identify path-level conflicts when both upstream and the preserved patch changed a file - leave governed conflict-resolution state ordinary author editing can resolve - establish canonical author-session provenance only after recovery state is internally consistent - allow commit/publication preflights only after conflicts and recovery verification pass - prefer a separately prepared recovery worktree (or equivalent transactional design) that freezes the original source until the new binding is verified ### Fail closed for - active original owner; foreign claimant; live/ambiguous competing session - mismatched repository, issue, branch, worktree, or heads - changed dirty fingerprints; unregistered/noncanonical paths - symlink or traversal escapes; unsafe lock-file symlinks - corrupt, partial, ownerless, or replayed journals - unexpected remote-head movement; unrelated conflicts ### Crash-safety / idempotence Test interruption: before journal persistence; after journal before recovery-worktree creation; after recovery-worktree before binding; after binding before completion; during retry. Retry must resume or fail safely without stealing ownership, duplicating worktrees, or losing dirty bytes. ## Acceptance criteria 1. Explicit recovery MCP operation accepts and verifies the authoritative expected values listed above. 2. Eligibility requires all gates in this issue; PID-less locks are never live by missing expiry alone. 3. Dirty bytes/fingerprints preserved; source worktree never cleaned/reset/deleted. 4. Crash journal precedes filesystem/ownership mutation; retries are idempotent. 5. Sync to pinned remote PR head with path-level conflict detection for overlapping dirty paths. 6. Canonical author-session binding only after consistent recovery state; commit/publication preflights recognize recovered provenance. 7. Fail closed for all cases listed above (active/foreign owners, mismatches, symlink attacks, journal corruption, remote drift). 8. Synthetic regression coverage modeled on #850 and #855 **without mutating their real state**, including: dead same-claimant; PID-less malformed lock; expired corroborating workflow lease; dirty registered worktree; older local / newer remote PR head; overlapping upstream change; byte-for-byte dirty preservation; session binding; preflight recognition; active/foreign refusal; fingerprint/head/path mismatches; symlink attacks; crash/retry idempotence; original source remains recoverable. 9. Focused + relevant full suites green (or baseline-proven pre-existing only against clean live master). 10. Single PR closes this issue only; #850, PR #853, #855, their worktrees, locks, fingerprints, and PR heads remain unchanged. ## Non-goals - Mutating, rebinding, synchronizing, cleaning, or publishing #850 or #855 - Merging current PR #853 tip or discarding its four unpublished #528 remediation files - Broad replacement for Issue #850 native bootstrap - Weakening ownership checks globally - Treating permission-level `allowed=true` as ownership authorization ## Canonical next step ```text STATE: ready-for-author WHO_IS_NEXT: author NEXT_ACTION: Allocate a clean registered worktree from live master, implement the recovery operation + tests, publish one PR, hand off to gitea-reviewer ```
jcwalker3 added the mcpstatus:readyworkflow-hardeningrecoverysafetytype:bug labels 2026-07-23 20:25:23 -05:00
jcwalker3 added status:in-progress and removed status:ready labels 2026-07-23 20:26:28 -05:00
Author
Owner

Issue claim heartbeat

<!-- gitea-issue-claim-heartbeat:v1 --> **Issue claim heartbeat** - kind: claim - issue: #860 - branch: fix/issue-860-dirty-orphan-worktree-recovery - phase: claimed - profile: prgs-author - pr: none - blocker: none - next_action: create worktree and begin implementation
jcwalker3 added status:pr-open and removed status:in-progress labels 2026-07-23 20:40:34 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Scaled-Tech-Consulting/Gitea-Tools#860