Skip to content

Missing SIGHUP handler causes orphaned processes to leak ~4.7 GB each after terminal death #14504

Closed

Description

@coleleavitt

Bug Report

Summary

When a controlling terminal is destroyed (SSH disconnect, tmux pane close, terminal emulator crash), opencode processes become orphaned and continue running indefinitely, each consuming ~4.7 GB of RAM. This is because opencode has no SIGHUP handler, and the TUI event loop blocks on stdin that will never produce input, preventing process.exit() in the finally block of index.ts from ever executing.

Observed Impact

On a system running opencode via oh-my-opencode with tmux, 14 orphaned opencode processes accumulated over time, consuming ~66 GB of RAM total. All had PPID=1 (adopted by init), no controlling terminal, and no alive children.

Root Cause

In packages/opencode/src/index.ts, the main flow is:

try {
  await cli.parse()  // blocks on TUI promise → stdin
} catch (e) { ... } finally {
  process.exit()     // only reached when cli.parse() resolves
}

When the TUI command runs (thread.ts), cli.parse() awaits the TUI promise (line 185), which waits for interactive terminal input. When the terminal dies:

  1. Kernel sends SIGHUP to the process group
  2. No SIGHUP handler exists → signal is ignored by the Bun event loop
  3. stdin is now a dead file descriptor but the TUI event loop doesn't detect this
  4. cli.parse() never resolves → process.exit() never runs
  5. Process continues consuming memory indefinitely as an orphan (PPID=1)

Environment

  • opencode v1.2.10
  • Bun 1.3.6
  • Linux (Arch)
  • oh-my-opencode v3.7.4 (manages tmux sessions)

Reproduction

  1. Start opencode in a tmux pane
  2. Kill the tmux pane or session externally (e.g. tmux kill-pane)
  3. Observe the opencode process continues running with PPID=1
  4. Repeat — each orphaned process retains ~4.7 GB

Proposed Fix

Add signal handlers before the main try block in index.ts:

for (const sig of ["SIGHUP", "SIGTERM", "SIGPIPE"] as const) {
  process.on(sig, () => {
    process.exit(1)
  })
}
  • SIGHUP: Terminal death (SSH disconnect, tmux pane close)
  • SIGTERM: Graceful shutdown requests
  • SIGPIPE: Broken pipe when output destination is gone

This ensures process.exit() is called immediately on terminal death, which triggers the existing subprocess cleanup logic and prevents orphaned processes.

Additional Context

The existing comment on lines 198-200 of index.ts already acknowledges that subprocesses can hang:

Some subprocesses don't react properly to SIGTERM and similar signals. Most notably, some docker-container-based MCP servers don't handle such signals unless run using docker run --init.

The same class of problem affects the parent opencode process itself — it doesn't react to SIGHUP at all.

Investigation Notes

  • Bun Workers are threads (via std.Thread.spawn() in web_worker.zig), not processes — they are not the source of zombie child processes
  • The orphaned processes were full opencode instances that lost their controlling terminal
  • Bun's process reaping (WaiterThread in process.zig) only handles children spawned via Bun.spawn(), not the parent process lifecycle

Activity

  1. github-actions commented on Feb 20, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    Please review these issues before continuing, and consider closing this one as a duplicate if they cover the same problem.

  2. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Feb 20, 2026
  3. added a commit that references this issue on Feb 20, 2026
    76ee4ab
  4. added a commit that references this issue on Feb 21, 2026
    b490a54
  5. OmerFarukOruc commented on Feb 27, 2026

    @OmerFarukOruc

    If you're hitting this on Linux — I wrote a cleanup script that finds and kills orphaned opencode processes (no TTY). Quick workaround until it's fixed.

    curl -fsSL https://gist.githubusercontent.com/OmerFarukOruc/d1bf2bb1f6f4f01c3a0e63288ada7c94/raw/oc-cleanup -o ~/.local/bin/oc-cleanup && chmod +x ~/.local/bin/oc-cleanup

    Usage: oc-cleanup (list), oc-cleanup kill (kill orphans), oc-cleanup watch (monitor all)

    Full gist + README: https://gist.github.com/OmerFarukOruc/d1bf2bb1f6f4f01c3a0e63288ada7c94
    See also: #15348

  6. WietRob commented on Mar 2, 2026

    @WietRob

    Confirming this issue with additional evidence from a different system.

    Environment

    • opencode 1.1.53 (brew)
    • Linux x86_64 (KDE/Wayland), 64 GB RAM
    • Repository: Mixed JS/TS + Python + Rust build artifacts

    Memory Evidence (smaps_rollup)

    Active terminal process:

    Rss:            37965896 kB    (~37.9 GB)
    Pss_Anon:       37925564 kB    (~37.9 GB anonymous)
    Private_Dirty:  37827316 kB    (~37.8 GB heap)  ← 99.9% of RSS
    Pss_File:          10269 kB    (~10 MB file-mapped)
    

    Orphaned process (TTY "?"):

    Rss:            13071736 kB    (~13 GB)
    Private_Dirty:  13039964 kB    (~13 GB heap)
    TTY:            ?
    PPID:           1 (systemd)
    

    Key Findings

    1. Memory is 99.9% Private_Dirty heap - not file cache, not LSP children. This is opencode itself allocating unbounded memory.

    2. Process tree confirms location:

      • Parent opencode: 37.8 GB
      • LSP children (biome, pyright): <1 GB combined
        → The leak is in the parent process, not MCP/LSP subprocesses.
    3. Orphaned process survives terminal - matches SIGHUP handler theory.

    Additional Context

    • High CPU during leak: 35-42% CPU usage even when "idle"
    • File descriptors: Only ~100-120 open files (not an FD leak)
    • Trigger: Medium/large monorepo with build artifacts (logs, Rust target/, node_modules)

    This strongly supports the SIGHUP handler fix proposed in this issue. The process receives SIGHUP on terminal disconnect but the handler doesn't trigger cleanup/exit, leaving orphaned processes that continue leaking heap memory.

    Happy to test any patches or provide additional diagnostics.

  7. WietRob commented on Mar 2, 2026

    @WietRob

    Configurable Memory Manager

    I've created an enhanced version of the cleanup script that addresses the multi-instance use case mentioned in this thread.

    Key Features

    • Per-instance limits: Each opencode instance limited to X GB (default 8GB)
    • Global limits: Monitors TOTAL memory across ALL instances (warn at 30GB, kill at 50GB)
    • Orphan detection: Kills processes with TTY "?"
    • Stuck detection: Kills processes with high RAM + low CPU for X minutes
    • Fully configurable: All thresholds in ~/.config/opencode/memory.conf
    • Easy install: Auto-installs cron job

    Why This Matters

    The original issue describes memory leaks growing to 60GB+. With multiple opencode windows:

    • Per-instance limits prevent any single instance from consuming too much
    • Global limits prevent the SUM from causing system OOM
    • Active processes (CPU > 5%) are NOT killed (unless they exceed hard limits)

    Installation

    curl -fsSL https://gist.githubusercontent.com/samuelstroschein/xxxxx/raw -o ~/.local/bin/opencode-memory-manager
    chmod +x ~/.local/bin/opencode-memory-manager
    
    # Edit config (optional)
    opencode-memory-manager config
    
    # Install cron job
    opencode-memory-manager install

    Configuration

    Edit ~/.config/opencode/memory.conf:

    PER_INSTANCE_LIMIT_GB=8      # Max per window
    GLOBAL_WARN_GB=30             # Warn when total > 30GB
    GLOBAL_CRITICAL_GB=40         # Critical when total > 40GB
    GLOBAL_MAX_GB=50              # Kill when total > 50GB
    
    STUCK_RAM_MB=5000             # 5GB
    STUCK_CPU_MAX=1               # < 1% CPU
    STUCK_MINUTES=10              # For 10 minutes
    
    CHECK_INTERVAL=2              # Check every 2 min

    Usage

    opencode-memory-manager status   # Show current status
    opencode-memory-manager run     # Run check manually
    opencode-memory-manager config   # Edit configuration
    opencode-memory-manager install  # Install cron job

    This tool has prevented several OOM kills on my system (93GB RAM, usually 7-10 opencode instances running).

    Happy to share the full script if there's interest in adding it to the project.

  8. thdxr commented on Apr 2, 2026

    @thdxr
    Member

    Closing in favor of #20695 where we're collecting all memory-related problems. Please follow that issue for updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

coreAnything pertaining to core functionality of the application (opencode server stuff)

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions