Skip to content

[Bug]: CLI opencode run exits prematurely when background tasks are active but no todos exist #3452

@CHLK

Description

@CHLK

Prerequisites

  • I will write this issue in English (see our Language Policy)
  • I have searched existing issues to avoid duplicates
  • I am using the latest version of oh-my-opencode
  • I have read the documentation or asked an AI coding agent with this project's GitHub URL loaded and couldn't find the answer

Bug Description

When using opencode run in CLI mode, the process exits prematurely if the main agent dispatches background subtasks (run_in_background=true) but has no incomplete todos. The main session goes idle, pollForCompletion() detects all completion conditions are met, and the process exits — even though background tasks are still running.

PR #217 (commit 8d9b68d8) attempted to fix this by checking for running background tasks in the todo-continuation-enforcer, but the fix has two gaps:

Root Cause Analysis

Gap 1: todo-continuation-enforcer only injects continuations when todos exist

In src/hooks/todo-continuation-enforcer/idle-event.ts, the handleSessionIdle() function checks for running background tasks (line 73-79), but this check is bypassed when there are no todos (line 110-114):

if (!todos || todos.length === 0) {
  return  // ← returns early, never injects continuation to keep session alive
}

This means: if the main agent dispatches background tasks without creating todos (a common pattern), the enforcer does nothing and the session goes idle.

Gap 2: Background task status check ignores "pending" tasks

The existing BG task check only looks for status === "running":

backgroundManager.getTasksByParentSession(sessionID)
.some((task) => task.status === "running")

Tasks queued for concurrency have status === "pending" — they have no session created yet, making them invisible to both the enforcer and areAllChildrenIdle().

Gap 3: checkCompletionConditions() has no direct BackgroundManager integration

src/cli/run/completion.ts relies solely on session.children() + session.status() to detect child activity. This can't see:

  • Tasks in "pending" state (no session created yet)
  • Tasks whose sessions have momentarily idle status
  • Tasks whose sessions are missing from the status response (treated as idle per commit 0d888df)

Steps to Reproduce

  1. Run opencode run with a prompt that triggers background subtask dispatch
  2. The main agent dispatches background task(s) with run_in_background=true
  3. The main agent's turn ends (session goes idle) before background tasks complete
  4. The CLI process exits prematurely with exit code 0
  5. Background tasks are orphaned/aborted during shutdown

Expected Behavior

opencode run should wait until ALL background tasks (including pending/queued ones) complete before exiting. The main agent should receive completion notifications and provide a final summary.

Actual Behavior

The process exits as soon as the main session goes idle, regardless of background task state. OPENCODE_BG_SHUTDOWN_WAIT=1 only delays the abort during shutdown — it doesn't prevent pollForCompletion() from returning prematurely.

Doctor Output

oh-my-opencode version: 3.17.3 
OpenCode: 1.4.5
OS: Linux

Error Logs

Configuration

Additional Context

Related

Proposed Fix

Use the existing run-continuation-state marker system (same as boulder/ralph-loop) to signal active background tasks across the process boundary:

  1. BackgroundManager writes a "background-task" continuation marker on launch() and clears it when all tasks complete/cancel/fail
  2. CLI checkCompletionConditions() reads this marker and blocks exit while active
  3. Fix BG task status checks to include "pending" alongside "running"

Operating System

Linux

OpenCode Version

1.4.5

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions