Skip to content

[Bug]: A backend killed by a signal is logged as "Failed to read the exit status" #15359

Closed

Description

@allisonmahmood

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

  1. With the desktop app running, kill -9 the backend (its pid is in ~/.t3/userdata/server-runtime.json).
  2. Look at ~/.t3/userdata/logs/server-child.log and 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

  1. juliusmarminge commented on Oct 3, 2026

    @juliusmarminge
    Member

    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 on handle.exitCode and maps every failure to BackendProcessExitStatusError. That error's message is always Failed 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 wraps Process interrupted due to receipt of signal: '<signal>' in a PlatformError. That's the normal exit event for SIGKILL, and for the reboot and quit-time kills you listed. A numeric exit, even a non-zero one, is still logged as code=<n>.

    finalizeRun saves only that public message (pid=<pid> <message> in server-child.log) and passes the same string to the backend exited unexpectedly; restart scheduled event. The signal survives only on the nested cause in desktop.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

    • One option is to treat Effect's signal interruption in runBackendProcess as an observed exit (for example signal=SIGKILL), written to both the child log and the restart event. BackendProcessExitStatusError would stay for status reads that really fail.
    • A unit test for the signal case would sit alongside the existing message test.

    #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.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 3, 2026
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

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions