Skip to content

fix(run): wait for background/child sessions before exiting#4

Merged
ranxianglei merged 3 commits into
masterfrom
fix/run-background-agent-exit
Jun 24, 2026
Merged

fix(run): wait for background/child sessions before exiting#4
ranxianglei merged 3 commits into
masterfrom
fix/run-background-agent-exit

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Problem

In opencode run mode, the event loop only watched the main session's idle status and exited as soon as it went idle. Background agents spawned via delegate_task(run_in_background=true) run in child sessions; when the main session finished, the process exited, disposing the instance and cancelling all runners — killing the still-running background agents. They could never complete.

Root cause

  1. delegate_task(run_in_background=true) creates a child session and starts it asynchronously, returning immediately.
  2. run.ts execute() returned right after sdk.session.prompt() resolved (main session idle).
  3. effectCmd finally calls store.dispose(ctx)SessionRunState finalizer cancels all runners → background child sessions interrupted.

Fix

  • Track every session's busy/idle status in the event loop via a busy set.
  • Gate execute()'s return on a done promise that resolves only once the main session is idle AND no background/child sessions remain busy.
  • Unreffed 2s fallback process.exit(0) for plugin handles that keep the event loop alive after all sessions complete.

Verification

Runtime-tested with isolated XDG_DATA_HOME (zero production impact):

  • No-background regression: EXIT=0 ✓
  • Background case: child session finish=stop (completes, not killed), done resolves correctly, clean EXIT=0 ✓

config.yml had blank_issues_enabled: false (inherited from upstream
opencode). Gitea reads this GitHub-compatible config and hides the
'Open a blank issue' option, so users could only pick from the 3
templates. Flip to true so plain issues are creatable.
In opencode run mode, the event loop only watched the main session's
idle status and exited as soon as it went idle. Background agents
spawned via delegate_task(run_in_background=true) run in child
sessions via prompt_async; when the main session finished the process
exited, disposing the instance and cancelling all runners, which
killed the still-running background agents.

Track every session's busy/idle status in the event loop and gate
execute()'s return on a 'done' promise that resolves only once the
main session is idle AND no background/child sessions remain busy.
Also resolve the promise if the SSE stream closes unexpectedly to
avoid hanging.
Plugin background handles (omo-stable's unawaited prompt promises and
task-manager timers) keep the Node.js event loop alive after every
session is idle, causing the run command to hang indefinitely.

Verified at runtime: done resolves correctly when all sessions idle
(confirmed via debug instrumentation), execute() returns, but the
process hangs on dangling plugin handles.

Add an unreffed 2s fallback exit timer after await done. unref() means
it never prevents natural exit (no plugin handles -> process exits
immediately, timer is moot); with plugin handles it force-exits after
2s. Without background agents behavior is unchanged (process exits
naturally before the timer fires).
@github-actions

Copy link
Copy Markdown
Contributor

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 2 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@github-actions

Copy link
Copy Markdown
Contributor

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@ranxianglei ranxianglei merged commit 139f011 into master Jun 24, 2026
3 of 8 checks passed
@ranxianglei ranxianglei deleted the fix/run-background-agent-exit branch June 24, 2026 14:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant