Repository navigation
Description
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/desktop
Summary
When the backend dies by signal, server-child.log and the desktop's "backend exited unexpectedly; restart scheduled" event say only Failed to read the exit status of desktop backend process <pid>. The actual signal is only in a nested cause in desktop.trace.ndjson, which rotates out within hours. A killed backend reads as if T3 lost track of its own child, and by the next day the evidence is gone.
Visual explainer, with every backend exit we could find: https://post.patchyhq.com/d/nt1pmpm8e0ae
Steps to reproduce
- With the desktop app running,
kill -9the backend (its pid is in~/.t3/userdata/server-runtime.json). - Look at
~/.t3/userdata/logs/server-child.logand the restart event.
Expected behavior
The log line and the restart event say the backend was killed by SIGKILL.
Actual behavior
# server-child.log and the "restart scheduled" event
Failed to read the exit status of desktop backend process 103797.
# desktop.trace.ndjson, runBackendProcess exit.cause (the only place the signal appears)
BackendProcessExitStatusError: Failed to read the exit status of desktop backend process 103797.
[cause]: PlatformError: Unknown: ChildProcess.exitCode (...)
[cause]: Error: Process interrupted due to receipt of signal: 'SIGKILL'Every backend exit still in our trace files reads this way: one caused by a reboot, two by quitting and relaunching the app, one by an unknown sender while the app kept running, and our own kill -9. Two earlier deaths now show only the log line, because their trace has rotated out, so their cause is lost.
Cause
DesktopBackendManager.ts:598 maps every handle.exitCode failure to BackendProcessExitStatusError. Effect's ChildProcess.exitCode fails when a child dies by signal, because there is no exit code to return. So the common case for a crashed or killed backend is reported as a read error.
Possible fix
Report a signal death as one, for example "backend killed by SIGKILL", in the persisted log line and the restart event as well as the trace. Keep the read-error message for when the status really can't be read.
Related
"Provider turn failed." drops its cause in the same way. #15011 and #15243 both found the real error only in a warning log, with the stored failure reduced to {"class":"transport_error","message":"Provider turn failed."}. We saw the same message when the app was quit mid-turn (makeProviderFailure, ProviderFailure.ts:137). I've left that to those issues.
Impact
Minor bug or occasional failure
Version or commit
0.0.46-nightly.20261003.2632 (f391794)
Environment
Linux 7.2.5 (Arch-based), desktop AppImage
Workaround
Read the nested exit.cause of the runBackendProcess span in desktop.trace.ndjson before it rotates.
Related reports from the same incident: #15357 (agents that were mid-turn survive the crash and keep working, unseen), #15358 (a thread waiting on background work stays stranded after a restart).
Filed by Allison Mahmood. Investigated and drafted with Claude Opus 5.5 in Claude Code.
Activity
juliusmarminge commentedon Oct 3, 2026
Note
Grok responding on behalf of Julius.
Triage
Thanks for the careful write-up, @allisonmahmood! This reproduces on current
main(5e35272fda). A backend that dies by signal gets reported as a failed exit-status read, and the signal never reaches the durable log or the restart event. I didn't find a duplicate or a fix PR.What happens
runBackendProcess(apps/desktop/src/backend/DesktopBackendManager.ts, around line 598) waits onhandle.exitCodeand maps every failure toBackendProcessExitStatusError. That error's message is alwaysFailed to read the exit status of desktop backend process <pid>.When Node reports no exit code, Effect's Node child-process spawner fails that wait and wrapsProcess interrupted due to receipt of signal: '<signal>'in aPlatformError. That's the normal exit event forSIGKILL, and for the reboot and quit-time kills you listed. A numeric exit, even a non-zero one, is still logged ascode=<n>.finalizeRunsaves only that public message (pid=<pid> <message>inserver-child.log) and passes the same string to thebackend exited unexpectedly; restart scheduledevent. The signal survives only on the nested cause indesktop.trace.ndjson, which rotates out. The restart itself still works. It's the recorded reason that's wrong.The existing test "preserves exit-status failures without copying their detail into the message" makes sense for a genuine read failure, because arbitrary cause text shouldn't land in the public message.
Likely fix area
runBackendProcessas an observed exit (for examplesignal=SIGKILL), written to both the child log and the restart event.BackendProcessExitStatusErrorwould stay for status reads that really fail.#15357 and #15358 come from the same incident but are different failures. The provider-turn cause drop stays with #15011 and #15243. A maintainer will decide on the fix direction.