stale index.lock from an interrupted commit blocks every later madtea write with a raw git error #319

Closed
opened 2026-07-21 13:33:20 +00:00 by hexajon · 1 comment
hexajon commented 2026-07-21 13:33:20 +00:00 (Migrated from codeberg.org)

Hit this live today: an madt_commit was killed mid-staging (the serve process lost its session - #4 territory), leaving .git/worktrees/<name>/index.lock behind. The next madt_commit on that worktree failed with git's verbatim message ("Unable to create ... index.lock: File exists. Another git process seems to be running..."), and so would every commit/finish after it, forever, until I removed the lock by hand.

Two problems:

  1. No detection. madtea just relays git's error. It fails fast (no stall), but the failure shape is precisely identifiable (lock-file-exists on add/commit) and recurs deterministically - a caller retrying madt_commit or madt_finish gets the identical error every time with no way forward inside the tool surface.
  2. No recovery guidance in house style. git's message explains the manual fix generically; madtea's teaching-error convention would name the exact lock path, the safety check (no live git process - e.g. pgrep - and lock mtime old), and the removal command.

What I want, in order of preference:

  • Detect the lock-exists failure in the commit/add/finish paths. Check whether any git process is alive and how old the lock is. If provably stale (no git process, lock older than a threshold), remove it and retry ONCE, saying so in the result. If not provably stale, refuse with the teaching error naming the lock path and the manual check - never remove a lock that might be held.
  • The same handling wherever else madtea shells into git write operations (commit, add, finish's commit leg, worktree ops).

Acceptance:

  • A commit against a provably-stale lock self-heals (removes lock, retries once, reports it did so)
  • A lock that cannot be proven stale produces a madtea teaching error naming the path and the manual verification, not git's raw text
  • A test pins both paths (fake lock file with and without a concurrent holder)

Bonus context for the "provably stale" heuristic: the lock in today's incident was zero bytes with an mtime matching the serve process's death, and pgrep showed no git processes - exactly the evidence a human uses.

Hit this live today: an madt_commit was killed mid-staging (the serve process lost its session - #4 territory), leaving `.git/worktrees/<name>/index.lock` behind. The next madt_commit on that worktree failed with git's verbatim message ("Unable to create ... index.lock: File exists. Another git process seems to be running..."), and so would every commit/finish after it, forever, until I removed the lock by hand. Two problems: 1. **No detection.** madtea just relays git's error. It fails fast (no stall), but the failure shape is precisely identifiable (lock-file-exists on add/commit) and recurs deterministically - a caller retrying madt_commit or madt_finish gets the identical error every time with no way forward inside the tool surface. 2. **No recovery guidance in house style.** git's message explains the manual fix generically; madtea's teaching-error convention would name the exact lock path, the safety check (no live git process - e.g. pgrep - and lock mtime old), and the removal command. What I want, in order of preference: - Detect the lock-exists failure in the commit/add/finish paths. Check whether any git process is alive and how old the lock is. If provably stale (no git process, lock older than a threshold), remove it and retry ONCE, saying so in the result. If not provably stale, refuse with the teaching error naming the lock path and the manual check - never remove a lock that might be held. - The same handling wherever else madtea shells into git write operations (commit, add, finish's commit leg, worktree ops). Acceptance: - [ ] A commit against a provably-stale lock self-heals (removes lock, retries once, reports it did so) - [ ] A lock that cannot be proven stale produces a madtea teaching error naming the path and the manual verification, not git's raw text - [ ] A test pins both paths (fake lock file with and without a concurrent holder) Bonus context for the "provably stale" heuristic: the lock in today's incident was zero bytes with an mtime matching the serve process's death, and pgrep showed no git processes - exactly the evidence a human uses.
hexajon commented 2026-07-21 13:40:30 +00:00 (Migrated from codeberg.org)

Proved the impact live, since we literally promise "re-run finish and it picks up where it left off":

What holds: the resume contract itself is real for post-commit failures - TestCommitStepResumesAfterCommitAlreadyLanded is green (re-running finish after the commit landed skips the commit leg and continues the ledger).

What breaks: a stale index.lock defeats the promise at step zero, in a loop. Reproduced on a probe branch: finish files=[...] against a planted zero-byte lock fails with staging failed: git add: fatal: Unable to create ... index.lock: File exists - git's raw text, and notably NO ledger and NO partial-progress line, because the failure happens in staging before the step machinery engages. An identical re-run produced a byte-identical error: zero progress, no self-heal, nothing pointing at the lock path as the fix. The re-run promise only starts working once a human removes the lock out-of-band.

So the acceptance list above is exactly right, plus one addition: the staging/commit leg of finish should fail through the same detect-or-teach path, and the failure should still produce the ledger shape (step 0 failed, nothing else ran) rather than a bare error - otherwise the documented resume contract has a hole precisely at its first step.

Proved the impact live, since we literally promise "re-run finish and it picks up where it left off": **What holds:** the resume contract itself is real for post-commit failures - `TestCommitStepResumesAfterCommitAlreadyLanded` is green (re-running finish after the commit landed skips the commit leg and continues the ledger). **What breaks:** a stale index.lock defeats the promise at step zero, in a loop. Reproduced on a probe branch: `finish files=[...]` against a planted zero-byte lock fails with `staging failed: git add: fatal: Unable to create ... index.lock: File exists` - git's raw text, and notably NO ledger and NO partial-progress line, because the failure happens in staging before the step machinery engages. An identical re-run produced a byte-identical error: zero progress, no self-heal, nothing pointing at the lock path as the fix. The re-run promise only starts working once a human removes the lock out-of-band. So the acceptance list above is exactly right, plus one addition: the staging/commit leg of finish should fail through the same detect-or-teach path, and the failure should still produce the ledger shape (step 0 failed, nothing else ran) rather than a bare error - otherwise the documented resume contract has a hole precisely at its first step.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sixfold-space/madtea#319
No description provided.