Repository navigation
Description
Feature hasn't been suggested before.
- I have verified this feature I'm about to request hasn't been suggested before.
Note: #27898 is related but broader (observing progress + streaming output). This issue is specifically about injecting a pending user message into a running session -- the mechanism the TUI already uses.
Describe the enhancement you want to request
OpenCode's background.extend() queues a new runTask() after the current run finishes, rather than injecting a user message into the active session loop. This means there is no API mechanism for a parent agent to send mid-execution instructions to a running subagent -- even though the TUI allows users to type messages while an agent is working.
Context
This was discovered while investigating why OmO's task(task_id=..., run_in_background=true) continuation messages never reach active subagents. The primary bug is in OmO (manager.resume() skips running tasks entirely -- filed separately on code-yeongyu/oh-my-openagent). OmO can work around the issue with a cancel-and-resume pattern, but that interrupts the subagent mid-execution and risks side effects (e.g., a bash command interrupted mid-way).
The proper fix requires OpenCode to support injecting a pending user message into a running session -- the same mechanism the TUI already uses when a user types while an agent is working.
How extend() works today
packages/core/src/background-job.ts:256-290:
const extend: Interface["extend"] = Effect.fn("BackgroundJob.extend")(function* (input) {
return yield* Effect.uninterruptibleMask((restore) =>
Effect.gen(function* () {
const tail = yield* Deferred.make<void>()
const result = yield* SynchronizedRef.modify(state.jobs, (jobs) => {
const job = jobs.get(input.id)
if (!job || job.info.status !== "running") return [{ extended: false }, jobs]
return [
{ extended: true, previous: job.tail, ... },
new Map(jobs).set(input.id, { ...job, pending: job.pending + 1, ... }),
]
})
if (!result.extended) return false
yield* fork(
result.scope, input.id, result.token, result.sequence,
Deferred.await(result.previous).pipe( // ← waits for current run to finish
Effect.andThen(restore(input.run)), // ← only THEN starts the new prompt
Effect.ensuring(Deferred.succeed(result.tail, undefined)),
),
)
return true
}),
)
})extend() returns true ("extended") but the new run is queued behind the current run's tail Deferred. It executes only after the subagent's entire loop finishes. In practice, the subagent completes and the session closes before the queued run starts -- so the prompt is effectively lost.
How task.ts uses extend()
packages/opencode/src/tool/task.ts:242-257:
if (yield* background.extend({ id: nextSession.id, run: runTask() })) {
return {
output: renderOutput({
sessionID: nextSession.id,
state: "running",
summary: "Background task updated",
text: BACKGROUND_UPDATED,
}),
}
}When extend() returns true, the tool reports "Background task updated" -- implying immediate delivery. The caller has no way to know the message is queued, not delivered.
Both code paths in task.ts fail for active sessions
Currently OmO's manager.resume() skips running tasks before reaching OpenCode's task tool. But if OmO were to fix its side and pass the prompt through, the OpenCode layer would still fail:
| Path | What happens | Result |
|---|---|---|
background.extend() returns true |
New run queued behind current run's Deferred | Prompt waits indefinitely, lost when session closes |
background.extend() returns false |
Falls through to background.start() |
start() checks existing?.info.status === "running" and returns existing info -- no-op |
Expected Behavior
Either:
extend()should inject the prompt as a user message into the active session (viasession/promptAPI), delivering it between tool call turns- Or
extend()should return a distinct status indicating "queued, not delivered" so the caller can handle it appropriately
The TUI already supports this
Users can type messages in the TUI while an agent is working. The message becomes pending and is injected at the next safe boundary between tool call turns. This mechanism already exists in OpenCode -- it just is not exposed through the programmatic API (task tool / background.extend()).
Exposing the same pending-message capability to the task tool would allow plugins like OmO to safely relay user instructions to running subagents without cancelling them.
Related
- [BUG] task(task_id=...) to running background subagent silently drops prompt and reports success code-yeongyu/oh-my-openagent#5966 -- OmO bug:
manager.resume()silently skips running tasks (primary issue) - stale synthetic continuation rows can overwrite the session model #33044 -- stale synthetic continuation rows can overwrite the session model
- Atlas hook injects boulder continuation into task-spawned worker subagents #18681 -- Atlas hook injects boulder continuation into task-spawned worker subagents
Environment
- OpenCode: 1.17.13
- oh-my-openagent: 4.15.1
Session IDs (from the incident that revealed this)
- Parent (Sisyphus):
ses_0ccebb6e7ffemS6uDn4OXsXywZ - Subagent (Sisyphus-Junior):
ses_0cccbc488ffeobMDPzuMAPt3EY - Timeframe:
2026-07-05 16:54 -- 17:17 UTC
Activity
github-actions commentedon Jul 7, 2026
This issue might be a duplicate of existing issues. Please check:
task(task_id=..., prompt=...)interrupts the subagent rather than injecting mid-turn, which is the same core limitation described here.cardil commentedon Jul 7, 2026
#27898 is broader (observing progress + streaming output). This issue is specifically about injecting a pending user message into a running session -- the mechanism the TUI already uses when a user types while an agent is working. If #27898 covers this, happy to close as duplicate.
github-actions commentedon Sep 6, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.