8000 2.6.0: recurring 0xc0000409 crash in wmux.exe, same fault offset every time (regression) · Issue #214 · amirlehmam/wmux · GitHub
[go: up one dir, main page]

Skip to content

2.6.0: recurring 0xc0000409 crash in wmux.exe, same fault offset every time (regression) #214

Closed

Description

@javierguevara-habi

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:

  1. Surfaces come back with new ids and lose their customTitle.
  2. Relaunched processes start with cwd C:\Windows\system32 instead of inheriting the
    pane's cwd. With Claude Code that files the session transcript under system32, so
    claude --continue from 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.
  3. Child processes die without a clean shutdown. Claude Code serializes its in-memory copy
    of ~/.claude/settings.json on 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

  1. amirlehmam commented on Sep 1, 2026

    @amirlehmam
    Owner

    Thank you — this report did the work. The six-crash table with the identical fault off 8000 set, and especially the main.log beside 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 your main.log (UTC), every one of the six lands on a will-quit line, to the second:

    crash (local) main.log (UTC)
    2026-08-28 20:57 01:57:05.809 will-quit ptys=5
    2026-08-28 21:22 02:22:26.493 will-quit ptys=7
    2026-08-29 23:09 04:09:39.498 will-quit ptys=6
    2026-08-31 16:23 21:23:40.411 will-quit ptys=6
    2026-08-31 18:39 23:39:03.990 will-quit ptys=7
    2026-08-31 22:47 03:47:06.201 will-quit ptys=2

    logDiagnostic('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:

    • It is not the file explorer. feat(explorer): file explorer panel and a read-only code surface #210 is not on this path at all, and your instinct to flag the correlation was reasonable — it just doesn't survive the timestamps. Sorry to take away the lead you offered; it was the right thing to offer.
    • You never lost a running session to this. Six of your thirteen quits crashed, at PTY counts from 2 to 7 — a coin-flip race, which is exactly why you couldn't find a sequence that triggers it. All the damage you described is in what the next launch restores.

    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() at will-quit fires all of them at once, and the process then walks straight into Node's environment teardown. A napi call that loses that race throws Napi::Error off the top of the stack, and the CRT's unhandled-exception filter calls __fastfail(FAST_FAIL_FATAL_APP_EXIT) — your 0xc0000409, parameter 7.

    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. resolveSpawnCwd returned undefined when nothing named a directory, which is node-pty's "inherit the parent's cwd", which after an OS relaunch is system32. 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. saveSession called itself an atomic write and wasn't: it unlinked the live session.json before 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-report now correlates each event against main.log and prints during: 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 an uncaughtExceptionMonitor so 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.json because it never gets a clean exit — is real, but it isn't caused by the crash. wmux force-kills each pane's process tree with taskkill /T /F on 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 give taskkill a 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 0xc0000409 after updating, wmux crash-report will now tell us in its own words whether it was at shutdown, which will make the next round much faster.

  2. added a commit that references this issue on Sep 1, 2026
  3. amirlehmam commented on Sep 1, 2026

    @amirlehmam
    Owner

    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::Error carries 'An exception is pending', which is node-addon-api's synthesized fallback, reachable only when can_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", behind NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS. node-pty never defines it, so the #else branch 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:

    • The drain narrows the window, in the same family as the session-end kill 1.1.1 added. Narrower is not closed.
    • The app.exit() is different in kind, and is the part I'd actually bet on: it means the environment teardown that produces can_call_into_js() == false doesn'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-report will now say whether it was at shutdown, which tells us immediately whether this held.

  4. amirlehmam commented on Sep 1, 2026

    @amirlehmam
    Owner

    Released in v2.8.0.

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

      0