Fix brainstorm server crashing on shutdown when the session dir is gone - #2139
Open
zigggy-stardust wants to merge 1 commit into
Open
Fix brainstorm server crashing on shutdown when the session dir is gone#2139zigggy-stardust wants to merge 1 commit into
zigggy-stardust wants to merge 1 commit into
Conversation
shutdown() wrote state/server-stopped without checking that STATE_DIR still exists. When the session directory is removed while the server sits idle, that write throws ENOENT from inside the lifecycle interval, so every cleanup step below it is skipped -- watcher.close(), clearInterval(), socket teardown and server.close() never run -- and the process dies on an uncaught exception with exit code 1 instead of exiting 0. State-dir bookkeeping is now best effort, matching the pattern already used for PORT_FILE: skip it when STATE_DIR is gone, guard it with try/catch, and always fall through to the real cleanup. watcher.close() is guarded too, since the content dir disappears in the same scenario. shutdown() is also made idempotent so a second lifecycle tick cannot call server.close() twice. The sentinel is deliberately not recreated when the directory is missing: nothing is left to consume it, and re-creating a tree the user just deleted would be surprising. Adds a lifecycle regression test that removes the session dir while the server is idle and asserts a clean exit 0 with no ENOENT.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
shutdown()inskills/brainstorming/scripts/server.cjswritesstate/server-stoppedwithout checking thatSTATE_DIRstill exists:If the session directory is removed while the server sits idle, the write throws from inside the
lifecycleCheckinterval. The throw is not caught, so every cleanup step below it is skipped and the process dies on an uncaught exception with exit code 1 instead of exiting 0.This is not just a cosmetic exit code — the watcher, the interval, the WebSocket sockets and
server.close()are all bypassed.How I hit it
A brainstorming session finished, the design was committed, and I deleted the scratch
.superpowers/directory. The server had already begun a normalidle timeoutshutdown, and supervision reported it as a failed process:Minimal reproduction
Fix
State-dir bookkeeping becomes best effort, matching the pattern already used a few lines below for
PORT_FILE(try { fs.writeFileSync(PORT_FILE, ...) } catch (e) { /* best effort */ }):STATE_DIRis gone, wrap it intry/catch, and always fall through to the real cleanupwatcher.close()too, sinceCONTENT_DIRdisappears in the same scenarioshutdown()idempotent so a second lifecycle tick cannot callserver.close()twiceThe sentinel is deliberately not recreated when the directory is missing — nothing is left to consume it, and re-creating a tree the user just deleted would be surprising.
Tests
Adds one regression test to
tests/brainstorm-server/lifecycle.test.js(port 3420, following the existing style in that file): remove the session dir while the server is idle, then assert a cleanexit 0with noENOENTon stderr.Verified both directions:
npm test13 passed, 1 failed)Behaviour on the normal path is unchanged — with the state dir present, the sentinel is still written and the process still exits 0.
Note on the one remaining failure
stop-server.sh :: persistent stop writes server-stoppedfails, but it is pre-existing and unrelated to this change. I reproduced it on pristinemain(b36e082) in the same environment, before and after applying the patch — identical6 passed, 1 failedboth times. I left it alone rather than widen the scope of this PR.Environment: macOS 25.6.0 (arm64), Node v26.5.1.