Files
open-design/apps/desktop/tests
lefarcenandGitHub 87a6a1a71a feat(desktop): observe abnormal exits (runtime 闪退) + child-process crashes (#5333)
* feat(desktop): observe abnormal exits (runtime 闪退) + child-process crashes

The crash telemetry only covered renderer crashes (desktop_renderer_crash) and
startup failures (packaged_runtime_failed). The most common "闪退" a user
reports — the app was running fine, then the whole window vanished — is a
MAIN-process crash / OS kill / force-quit after a hang, and none of it reaches
analytics: a dead process can't report itself, and uncaughtException/unresponsive
were log-only.

Add abnormal-exit detection via a session marker: beginDesktopSession stamps a
`clean: false` marker at launch; a graceful shutdown flips it clean. If the next
launch finds the previous marker still dirty, the previous run died without a
graceful quit — emit `desktop_unclean_exit` (previous version/session/started-at)
once the daemon is up to relay it. This catches the whole class regardless of
cause. Also emit `desktop_child_process_crash` on `child-process-gone` (GPU /
utility crashes — a common cause of a window that then goes blank).

Both events ride the existing free-form POST /api/observability/event (no
contract change), same as desktop_renderer_crash. The lifecycle logic is a pure,
unit-tested module; all fs/telemetry is best-effort and can never block startup
or shutdown.

* fix(desktop): only report unclean exit for runs that reached running

Review (nettee): stamping the dirty marker at startup meant a bootstrap failure
AFTER beginDesktopSession but before the app was up would leave the marker dirty
and be reported as desktop_unclean_exit next launch — but that's a startup
failure (already covered by packaged_runtime_failed), not a 'while running'
crash. Add a reachedRunning flag: the marker is written not-yet-running at
startup, markDesktopSessionRunning flips it true only once the runtime is up
(after onDesktopReady), and a previous run is reported ONLY when it reached
running and never went clean. This also makes each previous state reported at
most once (begin overwrites the marker).

* fix(desktop): mark session clean at end of shutdown, not start

Review (nettee): endDesktopSessionCleanly ran at the very start of shutdown(),
before the awaited teardown (beforeShutdown / IPC close / desktop.close /
app.quit). A quit that then hangs in one of those steps and is force-quit or
OS-killed would already have flipped the marker clean, hiding that abnormal
exit. Move the clean-mark to just before app.quit(), after teardown completes.

* fix(desktop): mark session running on actual reveal, not runtime creation

Review (nettee): createDesktopRuntime() starts async bootstrap with void tick()
and returns before the first window load/reveal, so calling
markDesktopSessionRunning right after it fired too early — a crash during the
first load would be mislabeled a runtime exit. Add an onRevealed callback fired
from revealMainWindow (the real 'app is up' moment: web app mounted + window
shown) and mark running from there instead.

* fix(desktop): persist unclean-exit until its report is acked

Review (nettee): beginDesktopSession overwrote the marker (dropping the crash
from disk) while the crash was only held in memory and reported via a
fire-and-forget POST that swallows failures — so a report that fails (daemon not
reachable yet, transient network) lost the signal permanently. Carry the crash
forward in the marker's unreportedCrash field; report returns whether it was
acked; clearReportedCrash removes it only on success, so a failed report retries
next launch. markRunning / clean preserve the carried-forward crash.

* fix(desktop): queue unreported crashes so consecutive failures lose none

Review (nettee): with a single carried-forward slot, a launch that is only
retrying crash A and then itself crashes replaced A with B on the next launch,
losing A. Carry a bounded queue (unreportedCrashes[], cap 20, oldest dropped)
instead: begin appends the previous run's crash to the carried list, each is
reported and cleared independently by sessionId, so a consecutive crash+failed-
report sequence keeps every crash. markRunning/clean preserve the queue.
2026-07-09 07:55:13 +00:00
..