Skip to content

Statusline crashes on Windows: Admission lock invariants hard-code '/' separator #2567

Open

Description

@bcbeatty

Version

LifeOS 8.761.0 · attestation: unattested (could not extract v8.761.0: tar (child): Cannot connect to C: resolve failed

gzip: stdin: unexpected end of file
tar: Child returned status 128
tar: Error is not recoverable: exiting now)

What is broken

On Windows the LifeOS statusline never renders: every run of LIFEOS_StatusLine.sh exits non-zero with InvariantViolation: render lock must be scoped to inflight. Admission builds directory and lock with path.join, which yields backslash-separated paths on win32, but the three invariants in acquire, reap and release compare against this.directory + "/render-", so the check can never pass. Related on the same platform: when settings.json statusLine.command holds a backslash path, case "$0" in */*) in LIFEOS_StatusLine.sh does not match and dir falls back to ., giving Module not found "./STATUSLINE/Render.ts".

Where (file:line)

LIFEOS/STATUSLINE/Admission.ts:52

Repro on a clean tree

1. Fresh install of LifeOS 8.761.0 on Windows 11 x64 via Claude Code (`InstallAll.ts --yes`), Git Bash shell, bun 1.4.4.
2. `echo {} | bash ~/.claude/LIFEOS/LIFEOS_StatusLine.sh` (forward-slash path, so `dir` resolves).
3. Render.ts throws `InvariantViolation: render lock must be scoped to inflight` from `Admission.acquire`.

Negative control

Probe: echo {} | bash C:~/.claude/LIFEOS/LIFEOS_StatusLine.sh · exit 1 · captured 2026-11-02T22:40:48.787Z

Red looks like: render lock must be scoped to inflight

20 |   }
21 | }
22 | 
23 | /** Throws InvariantViolation when `condition` is falsy; narrows types when it passes. */
24 | export function invariant(condition: unknown, message: string): asserts condition {
25 |   if (!condition) throw new InvariantViolation(message);
                             ^
InvariantViolation: INVARIANT VIOLATION: render lock must be scoped to inflight
      at invariant (~\.claude\LIFEOS\TOOLS\Invariant.ts:25:25)
      at acquire (~\.claude\LIFEOS\STATUSLINE\Admission.ts:52:5)
      at enter (~\.claude\LIFEOS\STATUSLINE\Admission.ts:36:15)
      at render (~\.claude\LIFEOS\STATUSLINE\Render.ts:50:28)
      at ~\.claude\LIFEOS\STATUSLINE\Render.ts:94:18

Bun v1.4.4 (Windows x64)

Suggested fix

Import sep from node:path and compare against this.directory + sep + "render-" in all three invariants. Verified green on this install after the change. Separately, the installer could write statusLine.command with forward slashes on win32 so the launcher's $0 directory detection works.

diff --git a/LifeOS/install/LIFEOS/STATUSLINE/Admission.ts b/LifeOS/install/LIFEOS/STATUSLINE/Admission.ts
index 8bfdf8b..a4f12bc 100644
--- a/LifeOS/install/LIFEOS/STATUSLINE/Admission.ts
+++ b/LifeOS/install/LIFEOS/STATUSLINE/Admission.ts
@@ -1,6 +1,6 @@
 import { mkdirSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
 import { availableParallelism } from "node:os";
-import { join } from "node:path";
+import { join, sep } from "node:path";
 import { invariant } from "../TOOLS/Invariant";
 import { mtime, readText } from "./Cache";
 import { wakePaths, type State } from "./State";
@@ -49,7 +49,7 @@ export class Admission {
   }
 
   private acquire(): boolean {
-    invariant(this.lock.startsWith(this.directory + "/render-"), "render lock must be scoped to inflight");
+    invariant(this.lock.startsWith(this.directory + sep + "render-"), "render lock must be scoped to inflight");
     try { mkdirSync(this.lock, { mode: 0o700 }); } catch { return false; }
     this.owned = true;
     try { writeFileSync(join(this.lock, "pid"), `${process.pid}\n`, { mode: 0o600, flag: "wx" }); }
@@ -58,7 +58,7 @@ export class Admission {
   }
 
   private reap(path: string): void {
-    invariant(path.startsWith(this.directory + "/render-") && path.endsWith(".lock"), "only render locks can be reaped");
+    invariant(path.startsWith(this.directory + sep + "render-") && path.endsWith(".lock"), "only render locks can be reaped");
     // Rename to an unoccupied path, never move a directory into another lock directory.
     const tombstone = `${path}.tmp.${process.pid}.${crypto.randomUUID()}`;
     try { renameSync(path, tombstone); } catch { return; }
@@ -66,7 +66,7 @@ export class Admission {
   }
 
   release(): void {
-    invariant(this.lock.startsWith(this.directory + "/render-"), "release must stay in inflight");
+    invariant(this.lock.startsWith(this.directory + sep + "render-"), "release must stay in inflight");
     if (!this.owned) return;
     this.owned = false;
     if (readText(join(this.lock, "pid"))?.trim() === String(process.pid)) this.reap(this.lock);

Verified after fix

Probe: echo {} | bash C:~/.claude/LIFEOS/LIFEOS_StatusLine.sh · exit 0 · captured 2026-11-02T22:40:49.036Z

�[0m            �[38;2;71;85;105m────────────────────────────�[0m �[38;2;148;197;253m▂�[38;2;74;127;224m▃�[38;2;148;197;253m▅�[38;2;27;71;255m█�[0m �[1m�[38;2;59;130;246mLifeOS�[0m �[38;2;59;130;246m8.761.0�[0m �[38;2;71;85;105m│�[0m �[38;2;125;207;255m�[0m  �[38;2;37;99;235mUNKNOWN�[0m  �[38;2;96;165;250m22:40�[0m  �[38;2;135;206;235m—�[0m �[38;2;71;85;105m────────────────────────────�[0m
�[0m            �[38;2;71;85;105m┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄�[0m

�[0m            �[38;2;71;85;105m────────────────────────────────────────────────────────────────────────────────────────────────────�[0m
�[0m                              �[38;2;187;154;247m�[0m �[38;2;148;163;184mMODELS:�[0m �[2;38;2;165;110;70mARGON�[0m �[2;38;2;160;140;70mGROK�[0m �[2;38;2;170;125;100mMUSE�[0m �[2;38;2;86;164;110mHAIKU�[0m �[2;38;2;90;130;185mSONNET�[0m �[2;38;2;140;150;170mLUNA�[0m �[2;38;2;70;130;165mSOL�[0m �[2;38;2;180;95;95mOPUS�[0m �[2;38;2;85;160;175mASTRA�[0m �[2;38;2;150;110;195mFABLE�[0m
�[0m            �[38;2;71;85;105m────────────────────────────────────────────────────────────────────────────────────────────────────�[0m
�[0m            �[38;2;122;162;247m�[0m �[38;2;165;180;252mCONTEXT:�[0m �[38;2;74;222;128m⛁�[0m�[38;2;74;222;128m⛁�[0m�[38;2;74;222;128m⛁�[0m�[38;2;74;222;128m⛁�[0m�[38;2;74;222;128m⛁�[0m�[38;2;74;222;128m⛁�[0m�[38;2;74;222;128m⛁�[0m�[38;2;74;222;128m⛁�[0m�[38;2;74;222;128m⛁�[0m�[38;2;74;222;128m⛁�[0m�[38;2;74;222;128m⛁�[0m�[38;2;74;222;128m⛁�[0m�[38;2;74;222;128m⛁�[0m�[38;2;74;222;128m⛁�[0m�[38;2;74;222;128m⛁�[0m�[38;2;74;222;128m⛁�[0m�[38;2;74;222;128m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;251;146;60m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;180;40;40m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m�[38;2;75;82;95m⛁�[0m �[38;2;125;165;190m23.4%�[0m  �[38;2;165;180;252mLD:�[0m �[38;2;251;146;60m11%�[0m
�[0m            �[38;2;71;85;105m────────────────────────────────────────────────────────────────────────────────────────────────────�[0m
�[0m                                       �[38;2;122;162;247m�[0m  �[38;2;194;139;62mSUBSCRIPTION�[0m �[38;2;71;85;105m│�[0m �[38;2;71;85;105mCC:�[0m �[38;2;100;116;<email>�[0m

Environment

  • OS: Windows (x64)
  • Harness: claude-code
  • bun: not found · git: not found
  • Doctor broken: identity-placeholders, codex, interceptor, cloudflare, ripgrep, imagemagick, gh, ffmpeg, ytdlp, fabric, jq

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions