Skip to content

dashboard-build.sh never completes on Windows under the detached VS Code SessionStart hook (3 root causes: build race, npm ci lifecycle-script node resolution, oversized PATH truncation) #113

Description

@smoochy

Summary

On Windows, the dashboard never starts under the detached VS Code SessionStart hook because plugin/scripts/dashboard-build.sh fails and retries forever. There are three independent root causes that stack; with all three the dashboard build never completes and ~/.claude-smart/dashboard-unavailable is rewritten every session. All three are present on main at v0.2.49 (the v0.2.49 Windows work in #99 touched dashboard-service.sh/_lib.sh/smart-install.sh, not dashboard-build.sh, which is unchanged since #65 / 2026-06-07).

Environment: Windows 11, Git-Bash (msys2), system Node v26.4.0 / npm 11.18.0 on PATH (so the private-node install is intentionally skipped by install_private_node). Claude Code VS Code extension (CLAUDE_CODE_ENTRYPOINT=claude-vscode).

The symptom is masked by --silent (see cause 2), so the failing log initially only shows a repeating:

[claude-smart] dashboard build: npm ci failed; removed partial node_modules
[claude-smart] dashboard: .next missing — starting background build
... (every session)

Root cause 1 — concurrent-build race clobbers node_modules

dashboard-build.sh is spawned near-simultaneously by two paths on session start (smart-install.sh Setup and dashboard-service.sh SessionStart). The single-flight lock has a TOCTOU hole: the pid file is written only after mkdir acquires the lock, so a peer arriving in that window sees the lock dir but no pid file. claude_smart_pid_alive_file returns false on a missing file, so the peer judges the lock stale, rm -rfs it and steals it. Two npm ci then run in the same dir, and each failure path does rm -rf node_modules, clobbering the other → the "npm ci failed" loop, .next never builds.

Fix: disambiguate "missing pid" by the lock dir's mtime — a young lock means a peer is mid-acquire (back off), an aged lock with no pid means the holder crashed before writing its pid (reclaim). Closes the race without reintroducing a permanent deadlock on a crashed holder.

Root cause 2 — npm ci aborts: a dependency postinstall can't resolve bare node

Once the race is fixed, npm ci still fails deterministically:

npm error command C:\WINDOWS\system32\cmd.exe /d /s /c node -e "import('./config/scripts/postinstall.js').catch(() => void 0)"
npm error Der Befehl "node" ist ... konnte nicht gefunden werden.   # 'node' not found

msw's postinstall shells out to bare node. npm.cmd itself runs (it locates node via its own %~dp0), but the cmd.exe grandchild npm spawns for the lifecycle script resolves node only through the inherited Windows %PATH%, which doesn't reliably carry the nodejs dir in the detached hook. npm 7+ no longer prepends node's execPath dir to a script's PATH, so the script fails and the whole npm ci aborts. dashboard-build.sh:113 also passes --silent, which hid this error entirely.

Fix: drop --silent (so failures are diagnosable) and install with --ignore-scripts. The dashboard builds and runs correctly without dependency lifecycle scripts (sharp etc. resolve via their prebuilt platform packages); npm's own newer default already blocks unlisted install scripts, so this just makes it explicit and portable.

Root cause 3 — oversized PATH truncated for cmd.exe children: next build → "next not found"

Same class, now at the build step:

> next build
Der Befehl "next" ist ... konnte nicht gefunden werden.

The detached hook's PATH is ~7–9 KB: claude_smart_prepend_node_bins pushes a (nonexistent, when a system node is used) private-node dir to the front, and repeated login-shell sourcing duplicates the whole list 3–4×. When npm's node process spawns a cmd.exe child for next build, that oversized environment is truncated for the child, dropping the entries the script needs (nodejs and npm-appended node_modules/.bin), so bare next isn't resolvable. Measured: the hook PATH reaches ~9 KB; an interactive build with the same content deduped to ~3 KB succeeds.

Fix: order-preservingly deduplicate PATH (anchoring the resolved nodejs dir at the front) before invoking npm, shrinking it back under the truncation threshold — the same small PATH under which an interactive build succeeds.

Reference implementation

All three are fixed and verified end-to-end (fresh install from a vendored copy → clean npm ci, next build completes, dashboard serves 200 on http://localhost:3001, race lock logs "peer acquiring lock; backing off") in these downstream patches against the vendored copy (path there is plugins/claude-smart/scripts/dashboard-build.sh):

  • smoochy/claude-marketplace#94 — race fix + drop --silent
  • smoochy/claude-marketplace#95 — --ignore-scripts + PATH dedup

Happy to open a PR porting these to plugin/scripts/dashboard-build.sh (+ optionally hardening claude_smart_prepend_node_bins) if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions