Skip to content

[Bug]: Desktop window process killed with SIGKILL (exit 9), automatically reloads while browser tabs disconnect #26942

Open

Description

@akash9676

Before submitting

  • Searched existing issues and PRs; related reports are linked below, but no confirmed duplicate was found.
  • Included the available incident details and their limitations.

Area

apps/desktop

What happened

An investigation reported an incident at 11:47:50 local time on 2026-10-29:

  • T3 Code's main window process was killed with SIGKILL / exit code 9.
  • T3 automatically reloaded the window within approximately one second, making it look like the app had restarted.
  • Browser tabs disconnected around the same time.
  • The main application process stayed running.

The distinction matters: this reports a window/renderer process termination, not a confirmed exit of Electron's main application process.

Steps to reproduce

No deterministic reproduction is known. The incident was found retrospectively in logs during investigation of an apparent restart. Slides and Care running together were mentioned as a possible correlation, but there is no evidence establishing either as the cause.

Expected behavior

The desktop window and browser tabs remain available during normal use. An unexpected process termination should leave enough diagnostic evidence to investigate its cause.

Actual behavior

The window was killed and automatically reloaded; browser tabs disconnected at roughly the same time. The available logs do not identify what sent the kill signal.

Impact

Occasional interruption of the desktop window and browser sessions; frequency and any lost work are not established.

Version or commit

Not supplied in the incident summary.

Environment

Desktop app. OS, installed app version, connection mode and incident timezone were not supplied; the filing environment should not be assumed to be the incident environment.

Evidence and diagnosis limits

The details above come from the reporter's investigation summary, not raw logs attached to this issue:

Main window process killed (SIGKILL, exit code 9); window automatically reloaded within a second; browser tabs disconnected around the same time; main app process stayed running.

The investigation found no recorded crash dump or out-of-memory event. That does not identify the killer or establish that memory pressure was impossible. No code or settings were changed during the investigation. Raw logs, process IDs and a deterministic trigger are not yet available in this report.

Related issues and PRs

These are related symptoms, not confirmed duplicates or confirmed fixes for this incident.

Workaround

The window reloaded automatically. No verified workaround to prevent recurrence is known.


Filed on the reporter's behalf by GPT-6.1 Sol through the Codex harness in T3 Code.

Activity

  1. juliusmarminge commented on Oct 29, 2026

    @juliusmarminge
    Member

    Tried this on main at 6d450a4 with the Linux desktop build (Electron 44, fresh T3 home):

    • kill -9 on the main window's renderer: renderer-history.ndjson logged render-process-gone, reason: "killed", exitCode: 9. The window reloaded on a new renderer 0.6 s later, and the main process and backend kept their PIDs.
    • The same kill with two in-app browser tabs open: both tabs went down with the window, as <webview> guests do. The reload recreated both at their URLs within about 2 s.
    • kill -9 on one browser tab's renderer: the window and backend were unaffected.

    So recovering on its own is the app working as intended. The open question is what sent the SIGKILL. T3 Code's own process cleanup only signals processes and process groups it spawned, and the app can't see who sent a SIGKILL to another process. That has to come from the machine where it happened. Could you add:

    1. OS and version, and the T3 Code version (Settings → About).
    2. The lines around 11:47:50 from ~/.t3/userdata/logs/renderer-history.ndjson: the render-process-gone record for "surface":"main" and the sample records before it. Their workingSetBytes show how large the renderer was when it died.
    3. On macOS, the system log for that minute, for example log show --start "2026-10-29 11:47:00" --end "2026-10-29 11:49:00" --predicate 'eventMessage CONTAINS[c] "jetsam" OR eventMessage CONTAINS[c] "memorystatus" OR eventMessage CONTAINS[c] "T3 Code Helper"'. A memory-pressure kill shows up there.
    4. Which "browser tabs" disconnected: the in-app browser tabs, or a separate browser (app.t3.codes or a paired web client) connected to this desktop? If they were in-app, did they come back after the reload?
    5. Whether an agent or a script was running commands that kill processes (kill, pkill, killing a port's owner) around that time.
  2. akash9676 commented on Oct 29, 2026

    @akash9676
    ContributorAuthor

    @juliusmarminge Here are the details collected directly from the affected laptop.

    1. OS, app and hardware

      • Omarchy 4.0.6 (Arch-based), Linux 7.2.8-9-omarchy, x86_64.
      • T3 Code 0.1.3, verified in the installed /usr/lib/t3code/resources/app.asar package metadata; Arch package t3code-bin 0.1.3-1.
      • Lenovo ThinkPad P15v Gen 1, model 20TQ0045GE.
      • Intel Core i7-10750H, 12 logical CPUs; approximately 32 GB RAM (30 GiB reported by free).
      • Intel UHD Graphics (CometLake-H) and NVIDIA Quadro P620.
      • Incident time: 2026-10-29 11:47:50 CET / 10:47:50 UTC.
    2. Renderer records

    The three preceding main-renderer samples, the termination record, and the next dom-ready record are below. These are selected fields from the actual records; unrelated fields are omitted.

    {"timestampUnixMs":1793270789166,"event":"sample","mainPid":539696,"surface":"main","rendererPid":539910,"memory":{"sampledAtUnixMs":1793270789166,"kind":"electron-process-working-set","workingSetBytes":574296064,"peakWorkingSetBytes":1033228288}}
    {"timestampUnixMs":1793270819171,"event":"sample","mainPid":539696,"surface":"main","rendererPid":539910,"memory":{"sampledAtUnixMs":1793270819171,"kind":"electron-process-working-set","workingSetBytes":573132800,"peakWorkingSetBytes":1033228288}}
    {"timestampUnixMs":1793270849176,"event":"sample","mainPid":539696,"surface":"main","rendererPid":539910,"memory":{"sampledAtUnixMs":1793270849176,"kind":"electron-process-working-set","workingSetBytes":548417536,"peakWorkingSetBytes":1033228288}}
    {"timestampUnixMs":1793270870365,"event":"render-process-gone","mainPid":539696,"surface":"main","rendererPid":539910,"memory":{"sampledAtUnixMs":1793270849176,"kind":"electron-process-working-set","workingSetBytes":548417536,"peakWorkingSetBytes":1033228288},"reason":"killed","exitCode":9}
    {"timestampUnixMs":1793270871225,"event":"dom-ready","mainPid":539696,"surface":"main","rendererPid":927268,"memory":null}

    The last sample was at 11:47:29.176 CET, about 21 seconds before the kill: 523 MiB working set, with a recorded peak of approximately 985 MiB. This is not a measurement at the instant of termination. The replacement renderer reached dom-ready 860 ms after the termination record; the main PID stayed unchanged.

    1. System logs

    This is Linux, so the macOS log command does not apply. The local journal for 11:47–11:49 CET contains no matches for oom, out of memory, killed process, memory pressure, or systemd-oomd. coredumpctl reports No coredumps found for that interval. This does not establish who sent SIGKILL or rule out every source of memory pressure.

    1. Which browser tabs

    The records confirm in-app preview/browser tabs were affected: three surface:"preview" records were marked destroyed at 11:47:50.319–.341 CET. After the main renderer reloaded, preview renderers were registered/created again around 11:47:57–59 CET, with a preview dom-ready at 11:47:59.331. So there is evidence of automatic tab recreation, although these records alone do not prove that every tab's contents and session state fully recovered. I have not established a separate external web-client disconnect.

    The backend also logged successful EnvironmentAuth.getSessionState calls starting at 11:47:51.774 CET. There is no observed stuck bootstrap error in this incident.

    1. Commands that kill processes

    Not established yet. I cannot confirm from the diagnostic records whether an agent or script executed kill, pkill, or a port-owner cleanup command at that time, and I do not want to claim this was ruled out.

    Reopened at the reporter's request so this can be investigated. Agreed that the automatic reload itself is expected recovery behavior; the unresolved part is the source of SIGKILL.

    — Posted on the reporter's behalf by Codex, using local read-only diagnostics.

  3. wimjan123 commented on Nov 2, 2026

    @wimjan123

    Another reproduction on v0.1.6 stable, Linux desktop. This one recurs: the main window's renderer was killed six times in one afternoon.

    Environment

    • T3 Code Desktop 0.1.6 (t3code-bin 0.1.6-1, Electron 44.10.0), local backend 0.1.6 in the same app.
    • Omarchy 4.0.6 (Arch + Hyprland, Wayland), Linux 7.2.8-9-omarchy, x86_64, 16 logical CPUs, 64 GiB RAM, RTX 3080.
    • Busy workload: about 14 Claude sessions running at once, plus agents running vitest, Playwright benches and builds. Load average about 12.5. 496 threads, statev2.sqlite about 1.9 GB.

    Kills (from logs/renderer-history.ndjson)

    Time (UTC) reason exitCode renderer working set
    2026-11-02 15:24:44 killed 9 473 MB
    2026-11-02 15:28:46 killed 9 333 MB
    2026-11-02 15:40:24 killed 9 563 MB
    2026-11-02 15:58:02 killed 9 517 MB
    2026-11-02 20:03:20 killed 9 517 MB
    2026-11-02 20:45:20 killed 9 595 MB

    Each time, dom-ready on a new renderer followed within one second. The main process (pid unchanged for 8.5 h) and the backend (pid unchanged for 5 h) kept running.

    What it was not

    • Not memory pressure. The renderer's cgroup (a Hyprland app-…scope) and its parent have memory.max/memory.high set to max. memory.events shows oom 0, oom_kill 0. There is no kernel OOM or systemd-oomd entry in the journal. During triage, shortly after the last kill, 29 GiB of RAM was available.
    • No core dump for t3code in coredumpctl. The system and user journals have nothing in the 15 s after the 15:58, 20:03 and 20:45 kills. I did not check the other three.
    • Not a process-group kill. The renderer shares its pgid with the Electron main process, and main survived, so the signal was sent to the renderer pid alone.
    • Not one of T3 Code's own SIGKILL paths, as far as the source shows. In v0.1.6, OpenCodeServerLedger.stopOrphan and PortScanner signal process groups and check identity first. DesktopWindow.ts only handles render-process-gone.

    Possibly related

    • The GPU process was running with --use-gl=disabled, and the renderer with --disable-gpu-compositing. The GPU process had started about an hour after the main process, so Chromium may have fallen back to software rendering after a GPU process restart. I have not confirmed a link to the kills.
    • Agents on this machine sometimes run broad pkill -f <pattern> commands. I checked the Claude transcripts for kill commands within 2 minutes after or 5 minutes before each kill. One match: a pkill -f "until ! pgrep" at 20:02:41, 39 s before the 20:03:20 kill. Its pattern should not match a T3 Code renderer, so this does not explain the kills. The other five have no candidate.

    Still unknown

    What sent the SIGKILL. An audit rule on kill syscalls targeting the renderer pid would identify the sender on the next occurrence, if a maintainer wants that data.


    Produced during a T3 Code triage session by Claude Opus 5.5 (claude-opus-5-5) in Claude Code, running inside T3 Code.

  4. juliusmarminge commented on Nov 12, 2026

    @juliusmarminge
    Member

    @akash9676 Could you record which process sends the SIGKILL the next time it happens? Run sudo auditctl -a always,exit -F arch=b64 -S kill,tkill,tgkill -F a1=9 -k t3-sigkill, then after the next render-process-gone run sudo ausearch -k t3-sigkill -i and post the record whose target pid matches the renderer pid, with its comm, exe, pid and ppid. That tells us whether it is Chromium's own browser or zygote process, a tool on the system, or an agent's command.

  5. cyppe commented on Nov 13, 2026

    @cyppe

    Update: sender found. It is the kernel RLIMIT_RTTIME check, armed with a limit of 0 by PipeWire, so T3 is the victim: #26942 (comment)

    Another data point from an Arch/Omarchy workstation (Linux 7.2, nightly 0.1.10-nightly.20261113, desktop app with the local backend, 10–20 Claude agents at once). Here it is mostly the backend child that gets SIGKILL, not only the renderer:

    UTC victim evidence
    11-08 09:36:29 backend server-child.log: killed by SIGKILL
    11-09 03:16:43 backend same
    11-10 14:20:47 backend same
    11-11 21:21:28 backend same
    11-13 08:06:17 backend same
    11-13 11:36:09 main renderer renderer-history.ndjson: render-process-gone, reason: killed, exitCode: 9
    11-13 14:11:40 backend same as the backend rows

    Each time the desktop logs "backend exited unexpectedly; restart scheduled", so the desktop did not stop the backend on purpose. Every agent ends with the backend (expected: the per-CLI group watcher sees its pipe close).

    What we ruled out:

    1. Not the OOM killer, systemd-oomd or systemd. The kernel log has no OOM record at any of these times, oomd logged nothing, and the user manager shows no stop or kill for the app scope. The agents slice is removed about 10 s after the backend dies, which is only the group watcher (kill -TERM 0; sleep 5; kill -KILL 0) running after the kill.
    2. Not T3's own process-group reaping, as far as we can read it. In ClaudeAdapterV2, the watcher and the exit handler signal only the CLI's own group: it is spawned detached, and signalProcessGroup refuses pid ≤ 1. The ps-based descendant killer keys on pid plus start time and walks only from the CLI pid. None of these paths can reach the backend's own group or the main renderer, and the renderer is not in the backend's tree at all.
    3. No agent command aimed at T3. We scanned every Claude and Codex transcript on the machine (about 3,600 files) for kill/pkill/killall/systemctl kill in the 3 minutes before each kill. Nothing targets T3. The only nearby kills run inside a container's own PID namespace or hit the caller's own children.
    4. PID reuse is fast here. With pid_max 4,194,304 and about 160 new PIDs a second, PIDs wrap roughly every 7 hours (the backend got pid 2393 one morning and 3,479,705 six hours later). Any tool that saves a PID and later sends kill -9 to it can hit an unrelated process, and the T3 processes are simply the most visible victims. This is our leading hypothesis, but it is not proven yet.

    The tracer we run now, instead of the auditd rule, since auditd sees only the four syscalls: bpftrace on rawtracepoint:signal_generate. That fires for kill/tkill/tgkill/pidfd_send_signal and also for cgroup.kill and the kernel OOM killer. It is filtered in the kernel to sig == 9 with a target in the app scope or an agent scope, and logs the sender's pid, comm, uid, cgroup, four ancestors and argv:

    rawtracepoint:vmlinux:signal_generate
    /args.sig == 9/
    {
      $tcg = args.task->cgroups->dfl_cgrp->kn->name;
      if (strncmp(str($tcg, 64), "app-com.t3tools.", 16) == 0 || strncmp(str($tcg, 64), "t3code-", 7) == 0) {
        $p1 = curtask->real_parent; $p2 = $p1->real_parent;
        printf("SIGKILL target=%d comm=%s cg=%s | sender pid=%d uid=%d comm=%s cg=%s | parents %d:%s < %d:%s\n",
          args.task->pid, args.task->comm, str($tcg, 64), pid, uid, comm,
          str(curtask->cgroups->dfl_cgrp->kn->name, 64), $p1->tgid, $p1->comm, $p2->tgid, $p2->comm);
      }
    }
    

    We will post the sender record after the next kill.

    One related observation: the CLI's group watcher stays in the app scope, while the CLI itself moves into its agent scope (systemd-run --scope). That is harmless for the watcher's own kill 0, but it puts a process from the app scope into each agent's process group.

  6. cyppe commented on Nov 13, 2026

    @cyppe

    We found the sender on our machine (Arch/Omarchy, Linux 7.2, nightly 0.1.10-nightly.20261113), and it is not T3. The SIGKILL comes from the kernel's RLIMIT_RTTIME check, armed with a limit of 0 by PipeWire. T3 is a victim. This probably explains the other Omarchy/Arch reports here.

    Record (bpftrace on rawtracepoint:signal_generate, at a backend kill):

    SIGKILL target=<tid> comm=WorkerThread leader=t3code group=1 result=0 | sender pid=<backend pid> tid=<same tid> comm=WorkerThread
    

    The target is the sending thread itself, with group=1. A user-space kill(getpid(), 9) from a thread records the leader as target (we checked), and tgkill records group=0. The shape matches check_rlimit() in kernel/time/posix-cpu-timers.c, which runs send_signal_locked(SIGKILL, SEND_SIG_PRIV, current, PIDTYPE_TGID) and prints nothing unless print_fatal_signals is set.

    Chain:

    1. Without rtkit installed, the xdg Realtime portal reports RTTimeUSecMax = 0.
    2. PipeWire's libpipewire-module-rt (1.6.9), loaded by every PipeWire client, clamps the client's RLIMIT_RTTIME to min(rt.time.hard, RTTimeUSecMax) = {0, 0}.
    3. The desktop shell is a PipeWire client (its volume widget), so every app launched from it inherits the hard limit 0. Check with grep 'realtime timeout' /proc/<t3 pid>/limits, which shows 0 0. The backend, renderers and every agent process inherit it from the desktop.
    4. check_thread_timers() compares rt.timeout (0 for a normal thread) with the hard limit 0. When any per-thread CPU-time timer fires on any thread, 0 < 0 is false and the whole process is SIGKILLed.

    Reproduction: in a process with RTTIME 0 0, a thread that arms timer_create(CLOCK_THREAD_CPUTIME_ID, ...) for 20 ms of CPU dies with 137, and its trace record matches the one above. With RTTIME unlimited, the same program survives and receives its timer signal.

    Workaround (no T3 change needed): stop PipeWire clients from clamping. Put context.properties = { module.rt = false } in ~/.config/pipewire/client.conf.d/50-no-module-rt.conf (or install rtkit), then restart the shell and T3. Running processes can be fixed in place with sudo prlimit --pid <pid> --rttime=unlimited:unlimited.

    One question for T3: something in the backend arms a per-thread CPU-time timer in a Node WorkerThread, about once a day here. None of the bundled native addons imports timer_create directly. Do you know what does (a profiler, a CPU budget per worker, an FFI call)? Making it tolerant here is not T3's job, but a check at startup would turn a silent kill into a clear error, for example a warning when getrlimit(RLIMIT_RTTIME) has a hard limit of 0.

    Known upstream: PipeWire https://gitlab.freedesktop.org/pipewire/pipewire/-/work_items/5429 (the clamp itself). Omarchy omacom/omarchy#16348 (fixed on main by installing rtkit) and omacom/omarchy#18741 (the 4.5.0 stable tag does not have that fix yet). We added our data there.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions