Skip to content

fix(actas): recover abandoned reclaim mutexes safely - #522

Open
yui-stingray wants to merge 2 commits into
fujibee:mainfrom
yui-stingray:fix/actas-reclaim-orphan-gc
Open

fix(actas): recover abandoned reclaim mutexes safely#522
yui-stingray wants to merge 2 commits into
fujibee:mainfrom
yui-stingray:fix/actas-reclaim-orphan-gc

Conversation

@yui-stingray

@yui-stingray yui-stingray commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Replace the abandoned .reclaim.d cleanup proposed in #68 with a crash-safe,
fail-closed serialization protocol for actas lock deletion.

The original age-based cleanup is not safe: a live mutator can be paused past
any threshold, and a delayed collector can remove a newly-created directory at
the same pathname. This change therefore never age-deletes a legacy marker.

What changed

  • Store each deletion mutex in a dedicated local SQLite database as
    (lock_key, holder_pid, 128-bit holder_generation).
  • Keep SQLite transactions short. The fixed helper owns the mutex, rechecks the
    exact lock state, and ends with exec rm, so killing the helper cannot leave
    an untracked destructive child running after its mutex appears stale.
  • Route stale reclaim, release, release-all, and stale GC through the same final
    mutation protocol.
  • Reclaim a dead mutex row only when its complete PID/generation tuple is still
    current. A paused live holder is never timed out.
  • Keep ordinary claims on the existing one-line owner record and avoid SQLite
    on the normal free-claim path.
  • Make claim failures structured and fail closed. Callers distinguish claimed,
    live-held, and infrastructure/reclaim errors; they never subscribe or write
    role affinity after a failed claim.
  • Make multi-pair rollback checked and explicit. If infrastructure failure
    prevents cleanup, callers report rollback=incomplete with the exact
    percent-encoded retained pairs instead of deleting without serialization or
    claiming an atomic group rollback.
  • Diagnose legacy .reclaim.d markers in SessionStart and document the safe
    transition: stop/restart every old mutator, validate each affected empty
    marker, remove only that marker manually, then retry.
  • Validate helper paths as direct children of the canonical run/ directory;
    relative, trailing-slash, and symlinked installation spellings converge on
    the same mutex key.

Scope and follow-up

This is a same-user concurrency-correctness primitive, not a security boundary
against arbitrary hostile same-UID filesystem/database mutation.

It intentionally does not add a per-claim generation to the actas record.
Same-owner successor fencing and the complete generation-bearing compare/delete
contract remain in #519. That follow-up can build on this mutex helper without
changing the #68 rollout boundary.

Because this replaces rather than implements #68's age-based acceptance text,
maintainer agreement on the recovery and rollout contract is requested before
merge. It addresses #68 but does not auto-close it.

Validation

  • Focused actas/integration/template tests: 74/74 passed
  • Requested cross-surface tests (actas, integration, instance ID, role session,
    despawn, Claude template): 163/163 passed
  • Final held-plus-incomplete contract tests: 27/27 passed
  • Current Bash syntax and GNU Bash 3.2 parse/functional smoke: passed
  • Targeted ShellCheck: passed
  • git diff --check: passed

No CI/workflow file is changed. The existing Windows job does not execute this
actas suite, so native Git Bash exec/PID and SQLite path behavior remains a
platform validation gap; I have left workflow changes out of this PR.

Rollout and rollback

  • Before using the new protocol, stop and restart every process that may still
    have sourced the old mkdir-based mutator.
  • Never remove a .reclaim.d marker based on age. Remove only a reported,
    validated empty marker after all old mutators are stopped.
  • Do not remove or replace run/actas-reclaim.db while a helper may be active.
  • Code rollback is straightforward, but stop active helpers first and retain
    the runtime DB unless its rows have been verified inactive.

Addresses #68. Related: #65, #519.

@yui-stingray
yui-stingray marked this pull request as ready for review July 29, 2026 00:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant