Skip to content

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

Open
yui-stingray wants to merge 3 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 3 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

Rebase the abandoned-reclaim-mutex fix onto current main (v1.1.13,
545973d) and make the related actas teardown paths fail closed.

The original age-based .reclaim.d cleanup proposed in #68 is unsafe: a live
mutator can be paused past any threshold, and a delayed collector can then
remove a newly created mutex at the same pathname. This change therefore never
age-deletes a legacy marker.

The rebase also closes teardown gaps found while reviewing the checked-release
contract. A watcher that claimed the same active name in multiple teams now
tears down its frozen startup set in a deterministic, retryable order instead
of dropping one team and leaving the others without a receiver.

What changed

  • Store deletion mutex ownership in a local SQLite database as
    (lock_key, holder_pid, 128-bit holder_generation).
  • Keep SQLite transactions short. A dedicated 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, checked release, release-all, and stale GC through the
    same final mutation protocol. Reclaim a mutex row only when its complete
    PID/generation tuple is still current.
  • Keep normal free claims on the existing one-line owner record, while making
    claim/reclaim failures structured and fail closed. Checked multi-pair
    rollback reports the exact retained pairs when infrastructure recovery is
    incomplete.
  • Make registry writes, reset publication, checked release, and rollback
    explicit about partial failure; a failed drop no longer proceeds to change
    the Claude active role or Monitor state.
  • Add an internal exact-team reset protocol with strict terminal records. Public
    reset/drop retain their all-team scope and partial-progress reporting; when
    checked release cannot be proven, they now exit nonzero rather than report a
    completed drop.
  • For graceful multi-team despawn, freeze the successfully claimed startup
    pairs, reset non-target pairs first, and reset the requested target last as
    the completion latch. After a committed non-target reset, rebuild the live
    SQL subscription and discard the stale fetched batch before processing more
    rows.
  • Keep partial teardown retryable. A failed non-target ready-sentinel cleanup
    remains in teardown bookkeeping so a later control can re-prove release and
    finish cleanup.
  • Strengthen leader completion: if a target ready sentinel existed at teardown
    start, success requires its absence. A different-owner replacement is not
    treated as proof and cannot cause a successor spawn record to be deleted.
  • Refuse force despawn before any provider mutation when a valid placement has
    another same-name registration. Single-target force cleanup uses the scoped
    machine protocol and retains recovery state on logical reset/release errors.
  • Diagnose legacy .reclaim.d markers during SessionStart and document the
    quiescent upgrade/rollback procedure.
  • Preserve the PID-provenance convention from fix(liveness): route every shell-minted pid through the local probe (#567) #584, including local probes for
    shell-minted helper PIDs.

Scope and coordination

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

#518 and #548 overlap with this PR in the despawn/watch/test surfaces and require
semantic reconciliation. The recommended integration order is #522, then a
semantic rebase of #518, then the safety redesign/rebase of #548; simple
conflict-marker removal would not preserve all three contracts.

Validation

  • Final exact-HEAD local Bats coverage exercised all 936 tests across a
    bounded serial run and continuation groups: 933 passed, 3 failed. The
    changed-surface files (actas_lock, actas_integration, despawn, and
    template guidance) contain 112 tests; all 112 passed. Two stable
    failures reproduce unchanged on clean v1.1.13:
  • The remaining failure matches the asynchronous process-lifecycle flake class
    tracked in bats suite flakes across the process-lifecycle tests: main itself fails ~half its runs, a different test each time #595: the replacement-dispatcher child-count assertion failed in
    the serial run and one focused rerun. The test, launcher, and its runtime-lock
    path are unchanged by this PR; the same focused command passed on clean
    v1.1.13.
  • Focused successor-ready/spawn replacement and committed-ready-cleanup retry
    regressions: passed.
  • Bash syntax for every changed shell script: passed.
  • Targeted ShellCheck: no new functional diagnostics (only existing
    source-following/locale and sourced-variable notices).
  • git diff --check: passed.
  • No CI or workflow file is changed.

GitHub's existing Linux and macOS full-suite jobs remain the authoritative
cross-platform gate. Native Git Bash and Bash 3.2 were not available in this
local environment; the existing Windows jobs cover only their current targeted
helper/runtime legs and do not exercise the full actas/despawn suite. No
workflow expansion is proposed here.

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.
  • Before rolling code back, quiesce new-protocol helpers and watchers. Retain
    run/actas-reclaim.db for inspection; do not delete it to force rollback.

Addresses #68 by replacing unsafe automatic marker cleanup with a fail-closed
recovery protocol. Related: #65, #514, #518, #519, #548, #556, #584, #595.

@yui-stingray
yui-stingray marked this pull request as ready for review July 29, 2026 00:01
@yui-stingray
yui-stingray force-pushed the fix/actas-reclaim-orphan-gc branch from a5a1d66 to e513f20 Compare August 2, 2026 10:27
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