Repository navigation
Description
Summary
wmux.exe dies with 0xc0000409 (STATUS_STACK_BUFFER_OVERRUN / __fastfail), always at
the same fault offset. The faulting module is wmux.exe itself — not a GPU driver, not a
system DLL.
This is a 2.6.0 regression. The Windows Event Log on this machine records zero wmux
crashes in the preceding 90 days. All six it has are 2.6.0, and the first one landed the
same day 2.6.0 was installed.
| 2.6.0 installed | 2026-08-28 13:21 |
| first crash | 2026-08-28 20:57 (7 h later) |
| crashes in the 90 days before | 0 |
| crashes since | 6 |
| exception code | 0xc0000409 (BEX64) |
| fault offset | 0x0000000003290be5 — identical across all six |
| module | wmux.exe 2.6.0.0, timestamp 0x6a3b18dc |
| OS | Windows 11 Pro 10.0.26200 |
An identical offset six times over suggests a deterministic code path rather than random
memory corruption. There is no memory pressure either — wmux sits around 1 GB across 5
processes when it goes.
Crash times
- 2026-08-28 20:57
- 2026-08-28 21:22
- 2026-08-29 23:09
- 2026-08-31 16:23
- 2026-08-31 18:39
- 2026-08-31 22:47
What the relaunch does afterwards
Each crash is followed by a relaunch, and the relaunch is where the real damage is:
- Surfaces come back with new ids and lose their
customTitle. - Relaunched processes start with cwd
C:\Windows\system32instead of inheriting the
pane's cwd. With Claude Code that files the session transcript undersystem32, so
claude --continuefrom the original folder answers "no saved sessions" — the history
looks lost when it is only misfiled. This is the one that hurts most in practice. - Child processes die without a clean shutdown. Claude Code serializes its in-memory copy
of~/.claude/settings.jsonon exit, so any edit made to that file while the session was
alive is silently reverted.
Suspicion
2.6.0 is the release that introduced the file explorer (#210). That is the strongest
correlation I have, but I have not confirmed it — I could not isolate a sequence that
triggers the crash on demand. Happy to run anything that would narrow it down.
wmux crash-report output
# wmux crash report
This output is what a wmux crash report should contain: the Windows crash
fingerprint and wmux's own process-lifecycle log. It carries no memory —
no environment variables, no pane contents, no command lines.
Do NOT attach a crash dump unless it is asked for by name. See
docs/crash-reports.md for what a Windows minidump actually contains.
platform : win32 10.0.26200
wmux version: 2.6.0
## Windows Event Log — Application Error, wmux.exe
- 2026-08-31T22:47:07.1414434-05:00
wmux version : 2.6.0.0
faulting module : wmux.exe
exception code : 0xc0000409
fault offset : 0x0000000003290be5
additional parameter: 0x0000000000000007 (BEX64)
- 2026-08-31T18:39:06.5144083-05:00
wmux version : 2.6.0.0
faulting module : wmux.exe
exception code : 0xc0000409
fault offset : 0x0000000003290be5
additional parameter: (no Windows Error Reporting record)
- 2026-08-31T16:23:43.7698898-05:00
wmux version : 2.6.0.0
faulting module : wmux.exe
exception code : 0xc0000409
fault offset : 0x0000000003290be5
additional parameter: 0x0000000000000007 (BEX64)
- 2026-08-29T23:09:40.7730531-05:00
wmux version : 2.6.0.0
faulting module : wmux.exe
exception code : 0xc0000409
fault offset : 0x0000000003290be5
additional parameter: 0x0000000000000007 (BEX64)
- 2026-08-28T21:22:27.7420964-05:00
wmux version : 2.6.0.0
faulting module : wmux.exe
exception code : 0xc0000409
fault offset : 0x0000000003290be5
additional parameter: 0x0000000000000007 (BEX64)
- 2026-08-28T20:57:06.7007253-05:00
wmux version : 2.6.0.0
faulting module : wmux.exe
exception code : 0xc0000409
fault offset : 0x0000000003290be5
additional parameter: 0x0000000000000007 (BEX64)
## wmux main.log (process lifecycle only)
2026-08-29T01:54:07.826Z pid=34056 start version=2.6.0 electron=43.0.0 guard=true
2026-08-29T01:57:05.809Z pid=34056 will-quit ptys=5 guard=true
2026-08-29T01:57:14.301Z pid=36576 start version=2.6.0 electron=43.0.0 guard=true
2026-08-29T02:22:26.493Z pid=36576 will-quit ptys=7 guard=true
2026-08-29T02:22:29.963Z pid=4120 start version=2.6.0 electron=43.0.0 guard=true
2026-08-29T02:24:01.109Z pid=4120 will-quit ptys=6 guard=true
2026-08-29T02:24:04.809Z pid=26020 start version=2.6.0 electron=43.0.0 guard=true
2026-08-30T04:09:39.498Z pid=26020 will-quit ptys=6 guard=true
2026-08-30T21:39:34.361Z pid=11472 start version=2.6.0 electron=43.0.0 guard=true
2026-08-31T19:24:51.696Z pid=11472 will-quit ptys=3 guard=true
2026-08-31T19:27:33.923Z pid=21056 start version=2.6.0 electron=43.0.0 guard=true
2026-08-31T21:23:40.411Z pid=21056 will-quit ptys=6 guard=true
2026-08-31T21:26:05.097Z pid=20284 start version=2.6.0 electron=43.0.0 guard=true
2026-08-31T23:39:03.990Z pid=20284 will-quit ptys=7 guard=true
2026-08-31T23:47:28.307Z pid=17852 start version=2.6.0 electron=43.0.0 guard=true
2026-08-31T23:56:20.491Z pid=17852 will-quit ptys=5 guard=true
2026-09-01T00:01:58.341Z pid=8328 start version=2.6.0 electron=43.0.0 guard=true
2026-09-01T00:09:09.148Z pid=8328 will-quit ptys=5 guard=true
2026-09-01T00:12:56.862Z pid=13416 start version=2.6.0 electron=43.0.0 guard=true
2026-09-01T00:25:24.640Z pid=13416 will-quit ptys=3 guard=true
2026-09-01T00:56:44.107Z pid=5084 start version=2.6.0 electron=43.0.0 guard=true
2026-09-01T00:59:31.594Z pid=5084 will-quit ptys=3 guard=true
2026-09-01T01:43:31.887Z pid=15088 start version=2.6.0 electron=43.0.0 guard=true
2026-09-01T03:47:06.201Z pid=15088 will-quit ptys=2 guard=true
2026-09-01T03:47:15.976Z pid=27976 start version=2.6.0 electron=43.0.0 guard=true
2026-09-01T04:03:06.154Z pid=27976 will-quit ptys=0 guard=true
2026-09-01T04:03:12.752Z pid=5780 start version=2.6.0 electron=43.0.0 guard=true
Activity
fix(quit): the 0xc0000409 abort is a shutdown race, not a runtime cra…
amirlehmam commentedon Sep 1, 2026
Thank you — this report did the work. The six-crash table with the identical fault off 8000 set, and especially the
main.logbeside it, is what made this findable, and it turns out to say something different from what we both assumed.Your crashes are shutdown crashes
Cross-referencing your crash times (local,
-05:00) against yourmain.log(UTC), every one of the six lands on awill-quitline, to the second:main.log(UTC)01:57:05.809 will-quit ptys=502:22:26.493 will-quit ptys=704:09:39.498 will-quit ptys=621:23:40.411 will-quit ptys=623:39:03.990 will-quit ptys=703:47:06.201 will-quit ptys=2logDiagnostic('will-quit', …)is the first statement in that handler, so the process reached shutdown, wrote the line, and then aborted inside it.Two consequences worth stating plainly:
What actually happens
wmux already had this mechanism written down, in a comment it had never connected to this signature. Every live PTY owns a node-pty ThreadSafeFunction whose ConPTY exit callback runs on the main thread as two napi calls in a frame with no handler above it.
killAll()atwill-quitfires all of them at once, and the process then walks straight into Node's environment teardown. A napi call that loses that race throwsNapi::Erroroff the top of the stack, and the CRT's unhandled-exception filter calls__fastfail(FAST_FAIL_FATAL_APP_EXIT)— your0xc0000409, parameter7.The existing
pty-crash-guard(from #150) closes the route where the JS callback throws. It cannot close this one, because here JS is never reached.Fixed in 2.8.0
The crash. Quit now holds itself open ~250 ms after killing the PTYs, so those callbacks land while the environment is still healthy, and then leaves via
app.exit()so whatever didn't land never sees the teardown at all. A quit with no PTYs still exits the ordinary way — the hard exit is a treatment for a condition, not a new way of quitting.cwd
C:\Windows\system32— the one you said hurts most, and you were right.resolveSpawnCwdreturnedundefinedwhen nothing named a directory, which is node-pty's "inherit the parent's cwd", which after an OS relaunch issystem32. It now falls back to%USERPROFILE%, the same fallback every other branch of that function already used. Anything that names a directory — a split,--cwd, "Open in wmux", a restored session, the starting-path setting — is unaffected.New surface ids / lost
customTitle.saveSessioncalled itself an atomic write and wasn't: itunlinked the livesession.jsonbefore renaming the temp over it, leaving a window with no session file at all. Dying there means the next launch has nothing to restore and comes up as a fresh Session 1 with everything re-minted. The unlink was never needed — libuv's rename already replaces on Windows — so it's now a single rename, with the old two-step kept only as a fallback for a real sharing violation.Diagnosability.
wmux crash-reportnow correlates each event againstmain.logand printsduring: shutdown (will-quit 1.2s earlier), plus a sentence saying so in words. Nobody should have to convert six local timestamps to UTC by hand to find this out. There's also anuncaughtExceptionMonitorso a JS-side death is distinguishable from a native abort in the log — observing only, it does not suppress anything.One thing I did not change
Your third point — Claude Code reverting
~/.claude/settings.jsonbecause it never gets a clean exit — is real, but it isn't caused by the crash. wmux force-kills each pane's process tree withtaskkill /T /Fon every quit, deliberately (#65: Claude's persistent backend and its MCP servers are grandchildren that nothing else reaps, so a graceful signal leaves them running). Making quit graceful-then-forceful is a genuine design change with a real "wmux won't close" failure mode, and I'd rather do it as its own issue than smuggle it into a crash fix. The new drain window does at least givetaskkilla moment to actually run before the process leaves, which it previously might not have had. Please open one if this keeps biting you — with the same quality of detail as this report, it'll get fixed.2.8.0 is building now. If you still see a
0xc0000409after updating,wmux crash-reportwill now tell us in its own words whether it was at shutdown, which will make the next round much faster.chore(cli): refresh the checked-in wmux.js after the #214 crash-repor…
amirlehmam commentedon Sep 1, 2026
One addition to the above, because I left out something you're owed.
The why underneath this was already settled in #150, by @Ray0483's minidump forensics, and my comment made the shutdown mechanism sound newly discovered when only the evidence that your six crashes are on that path is new.
The decisive detail there was a string: the thrown
Napi::Errorcarries'An exception is pending', which is node-addon-api's synthesized fallback, reachable only whencan_call_into_js() == false— i.e. the environment is terminating. node-addon-api has a guard for exactly this case, commented "the environment must be terminating… continuing will result in a fatal error", behindNODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS. node-pty never defines it, so the#elsebranch compiles in and the throw escapes with nothing above it.So the real fix is one line in node-pty's
binding.gyp, and it's open upstream as microsoft/node-pty#954 (still unmerged as of today). wmux ships node-pty's prebuilts and never compiles them, so it cannot define the macro downstream.That makes 2.8.0's change a mitigation, and I'd rather label it that way than oversell it:
session-endkill 1.1.1 added. Narrower is not closed.app.exit()is different in kind, and is the part I'd actually bet on: it means the environment teardown that producescan_call_into_js() == falsedoesn't run at all on that path. There is no window to lose if the process is already gone.I can't promise you it's impossible now — that promise belongs to #954. What I can say is that the path your six crashes took no longer exists, and that if one recurs,
wmux crash-reportwill now say whether it was at shutdown, which tells us immediately whether this held.amirlehmam commentedon Sep 1, 2026
Released in v2.8.0.