Skip to content
This repository was archived by the owner on Feb 25, 2026. It is now read-only.

fix: Close sub-agents, preventing them from becoming orphan processes#520

Merged
alex-alecu merged 4 commits intodevfrom
fix/fix-orphan-sub-agents
Feb 20, 2026
Merged

fix: Close sub-agents, preventing them from becoming orphan processes#520
alex-alecu merged 4 commits intodevfrom
fix/fix-orphan-sub-agents

Conversation

@alex-alecu
Copy link
Contributor

Context

Fix sub-agents becoming orphan processes.

Implementation

All 4 fixes implemented and typechecked. Here's a summary of what was changed:

src/cli/cmd/serve.ts

  • Added Instance import
  • Replaced dead-code await new Promise(() => {}) + unreachable server.stop() with an AbortController pattern: registers shutdown on SIGTERM, SIGINT, SIGHUP that calls Instance.disposeAll() then server.stop(true) then aborts

src/cli/cmd/web.ts

  • Added Instance import
  • Identical AbortController + signal handler pattern replacing the same dead-code block at the end of the handler

src/cli/cmd/tui/thread.ts

  • Added SIGHUP and SIGTERM handlers after the existing SIGUSR2 handler (line 129), calling client.call("shutdown", undefined) with .catch(() => {}) to handle the case where the worker has already exited
  • No SIGINT — the TUI handles Ctrl+C as a keyboard event

src/index.ts

  • Added Instance import
  • Added await Instance.disposeAll() in the finally block after telemetry shutdown and before process.exit() as a safety net (no-op if already disposed by signal handlers)

Screenshots

before after

How to Test

Run CLI and implement something - ask Kilo to spawn multiple sub-agents for each task - and look in Activity Monitor for "kilo" processes being closed after the implementation is done.

Get in Touch

We'd love to have a way to chat with you about your changes if necessary. If you're in the Kilo Code Discord, please share your handle here.

@alex-alecu alex-alecu self-assigned this Feb 20, 2026
@kilo-code-bot
Copy link
Contributor

kilo-code-bot bot commented Feb 20, 2026

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Overview

This PR adds graceful signal-based shutdown to the serve, web, and tui commands, plus a safety-net Instance.disposeAll() call in the main CLI finally block.

The implementation is clean and correct:

  • serve.ts / web.ts: Replaces the old unreachable await server.stop() (after await new Promise(() => {})) with a proper AbortController-based shutdown. Signal handlers (SIGTERM, SIGINT, SIGHUP) trigger Instance.disposeAll()server.stop(true), wrapped in try/finally to guarantee abort.abort() always fires.
  • tui/thread.ts: Adds SIGHUP and SIGTERM handlers that forward a shutdown RPC call to the worker, with .catch(() => {}) for resilience.
  • index.ts: Adds a safety-net Instance.disposeAll() in the finally block — a good defensive pattern since disposeAll() is idempotent (guards via disposal.all check).

Key correctness observations:

  • The try/finally pattern ensures abort.abort() is always called, even if disposeAll() or server.stop() throws
  • disposeAll() has internal deduplication — concurrent calls return the same promise, and subsequent calls after completion are safe (cache is empty)
  • AbortController.abort() is idempotent per spec, so repeated signals are harmless
  • Signal handlers are not removed after shutdown, but this is fine since process.exit() in index.ts terminates the process

Note on existing inline comments: The previous review flagged that abort.abort() might not be called if disposeAll() or server.stop() throws. This concern has been addressed — the current code already uses try/finally.

Files Reviewed (4 files)
  • packages/opencode/src/cli/cmd/serve.ts - 0 issues
  • packages/opencode/src/cli/cmd/web.ts - 0 issues
  • packages/opencode/src/cli/cmd/tui/thread.ts - 0 issues
  • packages/opencode/src/index.ts - 0 issues

@marius-kilocode
Copy link
Collaborator

Are you sure that this is only a subagent problem? Could also be that this happens for the main CLI alltogether right?

3 similar comments
@marius-kilocode
Copy link
Collaborator

Are you sure that this is only a subagent problem? Could also be that this happens for the main CLI alltogether right?

@marius-kilocode
Copy link
Collaborator

Are you sure that this is only a subagent problem? Could also be that this happens for the main CLI alltogether right?

@marius-kilocode
Copy link
Collaborator

Are you sure that this is only a subagent problem? Could also be that this happens for the main CLI alltogether right?

Wrap Instance.disposeAll() and server.stop() in try/finally so
abort.abort() always fires, preventing the process from hanging
if either call rejects.
Wrap Instance.disposeAll() and server.stop() in try/finally so
abort.abort() always fires, preventing the process from hanging
if either call rejects.
@alex-alecu alex-alecu merged commit c7c401a into dev Feb 20, 2026
7 checks passed
@alex-alecu alex-alecu deleted the fix/fix-orphan-sub-agents branch February 20, 2026 16:14
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants