Repository navigation
/cso on Windows: a dead-owner mutation lease is never reclaimed, bricking every later run #2894
Description
Summary
On Windows, gstack-cso never reclaims a mutation lease whose owner process is no longer live. Because every launcher invocation is its own short-lived process, each operation leaves a dead-owner lease that fails the next call — including a brand-new start in the same repo scope. Net effect is roughly one lease-taking operation per run directory, so a multi-step assessment cannot complete.
Version: gstack v1.87.4.0. Windows 11, Git Bash (MINGW64), freshly built helper (VS 2022 Build Tools 17.14.41).
Symptom
{"ok":false,"error":{"code":"UNSAFE_PATH","message":"Run mutation lease decision is malformed"}}
Sometimes the first failure instead reports:
{"ok":false,"error":{"code":"PERSISTENCE_FAILED","message":"Run mutation lease decision changed before cleanup"}}
Both inspect, read, history, finish, resume, and start return this once a stale lease exists. doctor keeps working, which fits — it takes no lease.
Reproduction
gstack-cso start --repo <repo> # ok, creates run
gstack-cso inspect <run> # ok
gstack-cso read <run> some.file # ok
gstack-cso read <run> other.file # UNSAFE_PATH — lease malformed
gstack-cso start --repo <repo> # also UNSAFE_PATH: the whole repo scope is now stuckWorkaround (discards the run, so it does not enable a real multi-phase audit): confirm no live gstack-cso process, then rm -rf ~/.gstack/security/cso/<repoId>/<runId>. The next start succeeds immediately.
Evidence that it is liveness, not corruption
The .decision file is well-formed 265-byte JSON and carries an ownerCreatedAt that would disambiguate a recycled PID:
{"schemaVersion":1,"token":"…","kind":"ticket","ticket":"0000000000000001",
"candidateDev":"…","candidateIno":"…","ownerPid":84108,
"ownerCreatedAt":1789645916568,"publisherPid":84108,"createdAt":1789645916592}Observed in both states, same error each time:
| ownerPid | state at failure |
|---|---|
| 56436 | recycled — PID now belongs to an unrelated conhost |
| 84108 | plain dead — no process with that PID |
So it is not specifically PID reuse. Any non-live owner fails, and the stored ownerCreatedAt appears not to be consulted. Windows recycles PIDs aggressively, which makes the recycled case common there, but the dead case alone is enough to reproduce.
The error message is also misleading: "malformed" points at parse/corruption and sends you looking at the file, when the file parses fine and the real condition is an unreclaimed lease.
Impact
/cso cannot complete an assessment on Windows. It is not loudly broken though, which is the part worth flagging: a run finished early still reports
No supported findings in the assessed scope.
while every domain in the same report reads not assessed. To a skimming reader that is indistinguishable from a clean security review. In our case the weekly /cso had been a no-op for a week before anyone noticed.
Suggested fix
Treat a lease as reclaimable when the owner is not live, using the ownerCreatedAt already stored to reject a recycled PID rather than trusting the PID alone. Separately, a distinct error code for "stale lease reclaimed/blocked" would stop this reading as file corruption.
Related, lower priority
./setup correctly refuses to keep a CSO helper it cannot rebuild and deletes it — good security posture — but on a machine without the MSVC toolchain that silently converts a working /cso into a fail-closed one on the next upgrade. The summary line does say CSO unavailable: ... ($CSO_FAIL_REASON), but a previously-working install going dark is worth a louder signal.
Activity
nattapol765-code commentedon Sep 18, 2026
Also hit this on Windows, gstack v1.87.4.0 — same symptoms, same workaround. One additional data point: the stale lease blocked
startacross three separate local clones that share one GitHub remote (different working directories, same repo). That suggestsrepoIdmay key off the remote URL rather than the local path, which could matter for the fix if it only checks PID liveness per-lease without considering that many clones can map to onerepoId.capy-ai commentedon Sep 23, 2026
Thank you @getabsolutehealth for the Windows lease failure report and @nattapol765-code for confirming that the symptom can block clones sharing the same repository identity.
#2942 landed a relevant underlying fix in
b9706f3,v1.88.1.0: NTFS device/inode identities are no longer rounded through JavaScript numbers. This repairs the demonstrated malformed-decision/changed-before-cleanup path identified precisely in #2927. The exact-identity tests include recovery of a genuinely dead owner, and the compiled-helper NTFS lifecycle passed on native Windows.Keeping this broader issue open. Dead-owner recovery already existed; the demonstrated repair is exact filesystem identity, not a new Windows process-creation-time check. It does not establish safe recovery when a PID has been recycled, and ambiguous legacy records are still refused. The report's separate diagnostic/report-wording and toolchain-upgrade concerns were not addressed by this integration either. Thanks for the evidence; those remaining items should not be marked fixed just because the NTFS numeric defect is now covered.
cawa1972 commentedon Sep 23, 2026
Data point after v1.88.1.0 (b9706f3) on Windows 11 Pro 10.0.26200 / NTFS, Git Bash:
the helper was rebuilt by
./setupfrom 1.88.1.0 sources (gstack-cso 3.0.0 (helper ABI 3)),doctorreportsstatic-snapshot: ready,and the first
startnow fails before any run is created:{"ok":false,"error":{"code":"UNSAFE_PATH","message":"Run mutation lease decision does not match its candidate owner"}}The repo's state namespace under
~/.gstack/security/cso/<repoId>/is empty before and after (the 1.87.x run that hit #2927 had been movedout of the state root). One other namespace exists on this machine for a different repo (different remote), holding a run created by 1.87.4.0
with its
.mutation-lock-leases/*.decision. We did not delete it (per your note that legacy records stay blocked). Ifstartconsults leasesoutside its own repo scope, that record may be what it trips on; otherwise this looks like the owner/creation-time check on Windows.
We made exactly one
startattempt, per the skill's one-run invariant.v1.91.16.0 fix: safe installs and upgrades, skills start everywhere, …
capy-ai commentedon Oct 3, 2026
Fixed in v1.91.16.0 (#3014, ac20ef1): Windows and macOS leases now carry a process start-time identity, reclaim is atomic, and one unreadable lease left by an older gstack no longer blocks
startor other runs. The native Windows lifecycle passes on thecso-windows-launcherCI job.Thanks for the report! If it still happens on v1.91.16.0 or later, reply here and we'll reopen.
v1.91.16.0 fix: safe installs and upgrades, skills start everywhere, …