Skip to content

LSP processes remain as orphaned zombie processes after opencode exits, causing severe memory leaks #18632

@jqlong17

Description

@jqlong17

Summary

After closing opencode, multiple LSP server processes remain running in the background indefinitely, consuming significant system memory. This appears to be a systematic issue with process lifecycle management rather than an isolated bug.

Environment

  • OpenCode version: Latest (built from source)
  • OS: macOS (darwin/arm64)
  • Node/Bun: Bun runtime
  • Installation: npm install -g opencode-ai

Current Behavior

When opencode is launched and then closed, LSP server processes spawned for various projects remain alive as orphaned processes:

# After running opencode a few times across different projects
$ ps aux | grep opencode | grep -v grep | wc -l
20

# Memory consumption per process
$ ps aux | grep opencode.*-c | awk '{sum += $6} END {print sum/1024 " MB"}'
~1.2 GB

Root Cause Analysis (from Source Code Review)

1. Missing Process Signal Handlers

In packages/opencode/src/index.ts, only SIGHUP is handled:

process.on("SIGHUP", () => process.exit())

Missing: SIGTERM, SIGINT handlers for graceful shutdown.

2. Forced Exit Without Cleanup Time

packages/opencode/src/index.ts:215:

finally {
  // Some subprocesses don't react properly to SIGTERM...
  process.exit()  // Immediately kills process, no cleanup time for LSP
}

This forces immediate exit without allowing Instance.dispose() or State.dispose() to complete.

3. Instance Cache Never Expires

packages/opencode/src/project/instance.ts:15:

const cache = new Map<string, Promise<Context>>()  // Permanent cache, no TTL

Instances (and their LSP clients) are never cleaned up automatically.

4. LSP Processes Not Properly Tracked

packages/opencode/src/lsp/server.ts uses spawn() without process group management:

  • No detached: true with process group tracking
  • Parent death doesn't trigger automatic child cleanup
  • Each new project creates new LSP servers that outlive the parent

5. State Disposal Timing Issues

packages/opencode/src/project/state.ts:39-46 warns about disposal taking too long (10s timeout), but the forced process.exit() in index.ts prevents disposal from completing at all.

Expected Behavior

  1. When opencode exits (normally or via signal), all child LSP processes should be terminated
  2. LSP servers should have idle timeout to auto-cleanup
  3. Orphaned processes from previous runs should be detected and cleaned up

Steps to Reproduce

  1. Open a project with opencode: opencode
  2. Work for a while (LSP servers will spawn)
  3. Close opencode (Ctrl+C or normal exit)
  4. Check for lingering processes: ps aux | grep opencode.*-c
  5. Repeat with different projects - processes accumulate

Proposed Solutions

Short-term (User Workaround)

Add to shell configuration:

# Kill orphaned opencode LSP processes on terminal exit
trap 'pkill -f "opencode.*-c"' EXIT

Long-term (Code Fixes)

  1. Add signal handlers in index.ts:

    process.on("SIGTERM", gracefulShutdown)
    process.on("SIGINT", gracefulShutdown)
    
    async function gracefulShutdown() {
      await Instance.disposeAll()
      process.exit(0)
    }
  2. Remove forced exit in index.ts finally block, or add delay:

    finally {
      // Give time for cleanup before forcing exit
      await new Promise(r => setTimeout(r, 2000))
      process.exit()
    }
  3. Add LSP idle timeout in lsp/index.ts:

    // Auto-shutdown LSP servers after idle period
    setTimeout(() => {
      if (noRecentActivity) client.shutdown()
    }, IDLE_TIMEOUT)
  4. Process group management in lsp/server.ts:

    spawn(command, args, {
      detached: true,
      // Track PIDs for cleanup
    })

Impact

  • Memory: Each LSP process consumes 50-100MB, easily accumulating to 1-2GB+
  • System resources: Zombie processes consume file descriptors and CPU cycles
  • User experience: System slowdown, need for manual cleanup

Related Code References

  • packages/opencode/src/index.ts:52, 215
  • packages/opencode/src/project/instance.ts:15, 103-105
  • packages/opencode/src/project/state.ts:31-69
  • packages/opencode/src/lsp/server.ts (all spawn calls)
  • packages/opencode/src/lsp/client.ts:238-244 (shutdown method exists but not called)
  • packages/opencode/src/lsp/index.ts:141-144 (dispose handler registered but not triggered)

Severity: High - affects all users with multi-project workflows
Type: Bug / Memory Leak

Metadata

Metadata

Assignees

Labels

coreAnything pertaining to core functionality of the application (opencode server stuff)perfIndicates a performance issue or need for optimization

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions