Repository navigation
Desktop hangs leave no record: a stall watchdog with sample, local crash dumps, and a quit deadline
#21316
josephv123
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Note
Claude Opus 5.5 in Claude Code, writing on behalf of @josephv123. Following up on #21301, as suggested there.
Problem
On 2026-10-18 the desktop main process on Joseph's Mac stopped making progress. It was running Nightly 0.1.1-nightly.20261018.3315 on macOS 27.0.1 (M5, 16 GB), under critical memory pressure: 28 GB of swap while 12 agents ran test suites.
#21301 has the details. It was closed because there is no stack, and so no root cause. The candidates (V8 heap thrashing through swap, a blocking native call on the main thread, a starved libuv pool) all leave the same record: nothing. A stack from the next hang would tell them apart.
Proposals
The four are independent.
Main-thread watchdog, with
sampleon macOS. The main thread writes a heartbeat to shared memory every second, and a worker thread checks it. After 10 s without one, the worker:main-thread-stalls.ndjsonin the log directory;/usr/bin/sample <pid> 5 -mayDie;wchan.It appends a second record with the stall's length when the heartbeat resumes.
S,futex_do_wait), is busy (R), or waits on disk or swap (D). If the libuv pool is starved instead, the heartbeat continues and no stall is recorded, even though spans stop reaching the disk. That rules out a stuck main thread.nodesampled itself. With the main thread blocked inAtomics.wait, all 4,216 samples ended inBuiltin_AtomicsWait → FutexEmulation::WaitSync → __psynch_cvwait. With a busy JavaScript loop they were in interpreter and JIT frames. Not yet run inside the packaged app, which is also hardened-runtime signed.unresponsive/responsivelogging on app windows.Local-only
crashReporter.crashReporter.start({ uploadToServer: false })keeps Crashpad minidumps inapp.getPath("crashDumps")and uploads nothing.Quit deadline. After 10 s, the app logs which shutdown step is stuck, still requests the backend stop, and waits 2 s so the trace file keeps the warning. Then it calls
app.exit(0).Fixes: quit or logout blocked forever by a shutdown step that never finishes. Today the window-bounds write has no timeout. A backend stop also takes the instance lock before its 5 s timeout starts.
Limits: as you pointed out, the deadline runs on the main thread. It only helps when main is alive but a shutdown step never completes, so it would not have fired in [Bug]: A hung desktop main process leaves no record and blocks macOS logout until force-quit #21301. A forced exit skips the remaining finalizers, which is safe for two reasons:
fix(desktop): let lifecycle shutdown finish before exiting #20303 would raise the backend stop timeout to 45 s, and the deadline would have to stay above it.
Prototype:
mainand pass with the change.app.quit()as before.Prototype
The branch https://github.com/josephv123/t3code/tree/feat/desktop-hang-diagnostics has the quit deadline and the watchdog as two separate commits, with tests. In
apps/desktop,vp test run src/app/DesktopLifecycle.test.ts src/telemetry/MainThreadWatchdogWorker.test.tsgives 10 passed. Theunresponsivelogging andcrashReporterare not prototyped.Question
Which of these would you take as focused PRs? The watchdog is the one that would have given #21301 a stack. Joseph can run any of them on his Mac.
Related: #10911, #21189, #20395, #20329.
All reactions