Repository navigation
Description
Bug: promptAsync message persisted but session loop never scheduled — background-agent wake silently dropped
Environment
- opencode version: 1.17.4
- Platform: Linux (x86_64)
- Plugin involved:
oh-my-openagent/ OhMyOpenCode background-agent (the dispatcher of the wake prompt — included for context; the drop itself happens server-side) - Storage: default sqlite (
~/.local/share/opencode/opencode.db, WAL mode)
Summary
A plugin dispatched two async prompts (source: background-agent-parent-wake) to an idle primary session. The server accepted and persisted both as user messages in the DB, but never scheduled an assistant loop for them — no loop/process log entries, and no error of any kind. The messages sat unprocessed for ~9 minutes until the human user manually typed a new message, which started a normal loop that then consumed the stale notifications.
Net effect: background-task completion notifications that are supposed to wake the parent session are silently lost. The user has to "nudge" the session manually to get results.
Timeline (UTC, cross-referenced from opencode.log, plugin log, and opencode.db)
| Time | Event | Evidence |
|---|---|---|
| 07:34:14 | Parent session ses_14560fa3... finishes its turn (exiting loop), goes idle |
server log |
| 07:35:10.7 | Background task #1 completes → plugin dispatches wake #1: promptAsync dispatched {source: background-agent-parent-wake} |
plugin log |
| 07:35:10 | Wake #1 persisted as msg_ebac19a7d... (role=user) in parent session |
opencode.db message table |
| 07:35:32.8 | Background task #2 completes (allComplete=true, shouldReply=true) → wake #2 dispatched |
plugin log |
| 07:35:32 | Wake #2 persisted as msg_ebac1f0f2... (role=user) |
opencode.db |
| 07:35:32 → 07:44:14 | Zero server activity for this session. Grep over the window shows 0 lines for the main process/session: no loop, no process, no error |
server log |
| 07:35:37+, every 5s | Plugin watchdog: Kept dispatched parent wake awaiting late failure or assistant output — waits forever |
plugin log |
| 07:44:14 | Human user types a message → loop step=0 starts; the two stale wake messages are finally pulled into context as history |
server log + db |
DB snapshot of the parent session (note two consecutive user messages with no assistant message between them and the next user message 9 minutes later):
msg_ebac0a758... assistant 07:34:08
msg_ebac19a7d... user 07:35:10 <- wake #1: persisted, never executed
msg_ebac1f0f2... user 07:35:32 <- wake #2: persisted, never executed
msg_ebac9e56f... user 07:44:14 <- human user's manual message
msg_ebac9e57f... assistant 07:44:14 <- first assistant activity after 07:34
Suspected cause
The failure mode is "accepted but never scheduled": the HTTP handler for the async prompt wrote the message, but the step that enqueues/starts the session loop never ran, and no error surfaced.
One suspicious correlation: at 07:35:10.99 and 07:35:32.80 — i.e., within ~300ms of both wake dispatches — a second opencode process was bootstrapping in the same directory (creating instance directory=..., separate run id), sharing the same opencode.db. Both wakes were dispatched exactly while the second instance was initializing / creating its own session. Possible directions:
- Race between message persistence and loop scheduling when another instance holds the DB (WAL) or the event bus — the write succeeds, the "kick the loop" step is lost.
- Instance routing: the prompt was stored by one instance but the loop-scheduling event was observed (and ignored) by the other.
- A path in the async prompt handler where, if the session lock/queue is contended at just the wrong moment, the request returns success after persisting the message without enqueueing execution.
Also worth noting: the same drop happened twice in a row (07:35:10 and 07:35:32), so it is reproducible under these conditions, not a one-off.
Fix suggestions
- Transactional coupling: persisting a prompt message and scheduling its execution should be atomic from the caller's perspective — if the loop cannot be scheduled, the API should fail loudly instead of returning success.
- Startup/idle reconciliation sweep: on instance start and/or periodically, scan for sessions whose latest message is
role=userwith no in-flight loop and either schedule execution or emit a warning. This converts silent data loss into self-healing. (This sweep would also have recovered both lost wakes here within seconds.) - Observability: log an explicit line when an async prompt is accepted (
session.prompt accepted id=... scheduled=true/false). Today the gap between "message in DB" and "no loop" is invisible without manually diffing the DB against the log. - Multi-instance hardening: if two instances on the same project directory are not a supported configuration, detect and warn; if supported, make loop scheduling robust to a concurrent instance bootstrapping.
Impact
Any plugin- or system-originated async prompt (background-task completion wakes, continuation loops, etc.) can be silently dropped. The session looks healthy; the user just never gets the follow-up turn and has to prompt manually. This undermines every "notify the parent session when done" workflow.
Happy to provide the full sanitized log excerpts for the window above if useful.
Activity
github-actions commentedon Jun 12, 2026
This issue might be a duplicate of existing issues. Please check:
prompt_asyncmessage accepted and persisted but session loop never triggered for an idle session; user has to manually type to get the session to process the injected message. The race condition hypothesis also matches.promptAsyncreturns 204 but background work runs in wrong instance context, which may contribute to loop scheduling failures.If your issue is distinct (e.g., specifically the multi-instance / concurrent DB write scenario you identified), please note that in a comment so maintainers can track the differences.
zswll2 commentedon Jun 17, 2026
Additional Reproduction — oh-my-openagent 4.11.0 + opencode 1.17.7
Confirming this issue persists on the latest versions:
Environment:
1.17.74.11.0(released Jun 17, includespromptAsyncReservations+ PR plugins:file.editedevent missingbeforetext, hard to extract edit locations #5171 noReply gate fix from oh-my-openagent)volcengine-plan/glm-5.1Reproduction:
<system-reminder>notifications are injected:OMO_INTERNAL_NOREPLY(forceNoReply safety override — expected)shouldReply=true(allComplete=true) — should trigger agent turnObservations:
file.editedevent missingbeforetext, hard to extract edit locations #5171,messageIsTerminalNoReplyUser) is confirmed present in the installed bundle — the noReply deadlock from issue [Bug]: Background task completion reminder does not wake parent assistant code-yeongyu/oh-my-openagent#5147 is resolved on the gate side.promptAsyncReservationstracking (new in 4.11.0) is also present.session.promptAsync()→ HTTP POST → message persisted → session loop never started.Key evidence that this is server-side:
The notification text and markers are correct (second notification omits NOREPLY, signaling reply-required). The client dispatch path (
sendParentWakePrompt → dispatchAfterSessionIdle → promptAsync) executes without error. The message appears in session history. But no LLM generation cycle is triggered by the server until a real user message arrives.Impact: All background-agent workflows that rely on
shouldReply=trueauto-resume are broken. The parent agent can never automatically collectbackground_output()results without manual user intervention.Happy to provide session logs or run any diagnostic commands if helpful.
zswll2 commentedon Jun 17, 2026
Root Cause Pinpointed:
ProviderModelNotFoundErrorswallowed bycatchCauseinpromptAsynchandlerFollowing up on the previous reproduction. I traced the full dispatch path through the opencode 1.17.7 binary and found the specific failure point — it's not a vague "scheduling gap" but a concrete error being silently swallowed.
The swallowing mechanism
SessionHttpApi.promptAsyncwrapsSessionPrompt.promptin acatchCausethat logs the error but never re-throws, then unconditionally returnsNoContent(HTTP 204):The
SessionPrompt.prompthandler callsSessionPrompt.loopwhennoReply !== true:Log evidence (5 occurrences)
All
prompt_asyncinvocations that should trigger a reply (noReply=false) fail withProviderModelNotFoundErrorduring model resolution inside the loop. The error is caught, logged at ERROR level, and the HTTP response is still 204 — so the caller (oh-my-openagent's parent-wake dispatch) sees success and assumes the wake worked.Why this is a double-blind failure
sendParentWakePrompt → dispatchInternalPrompt → promptAsyncreceives HTTP 204 → treats as accepted. No retry, no error.catchCauseswallows the error → loop never starts → no LLM generation.Neither side surfaces the failure to the user. The parent session appears frozen until a real user message arrives (which goes through the synchronous
session.promptpath with a complete model payload and succeeds normally).Likely trigger
The
promptAsyncrequest from oh-my-openagent's parent-wake may not include amodelfield in the body (it's conditionally included based on whether the parent session's message history yields a model). When the model is absent,SessionPrompt.loop → resolveModelfails. The synchronoussession.promptpath doesn't hit this because the TUI always sends the full model in the payload.Suggested fix (server-side)
The
catchCauseinSessionHttpApi.promptAsyncshould not swallow errors unconditionally. At minimum,ProviderModelNotFoundError(and similarDieerrors) should propagate or trigger a fallback model resolution rather than being silently dropped. Alternatively,SessionPrompt.loopshould fall back to the session's configured/default model when the resolved model is missing.Environment: opencode 1.17.7, oh-my-openagent 4.11.0, Linux x64
zswll2 commentedon Jun 20, 2026
Closing — Root cause was environmental, not a code bug
After further investigation, the
promptAsync failed: ProviderModelNotFoundErrorerrors were caused by multiple opencode server instances running simultaneously on the same machine:opencode serve --port 4096instance (running as systemd service)When oh-my-openagent's parent-wake mechanism called
promptAsyncfor the parent session, the SDK client routed the API call to the headless serve instance (port 4096) instead of the TUI instance that owned the session. The serve instance couldn't properly resolve the session's model context, resulting inProviderModelNotFoundError.Fix: Stopping the redundant
opencode-serve.servicesystemd unit resolved the issue completely. Parent-wake notifications now route to the correct TUI instance and work as expected.This was NOT a bug in opencode's
SessionHttpApi.promptAsyncorSessionPrompt.loop()— the error swallowing behavior (HTTP 204 on failure) is still worth improving for debuggability, but the root cause was instance conflict, not missing model resolution.Closing this issue.
zswll2 commentedon Jun 20, 2026
Closing — environmental issue (multiple opencode instances), not a code bug. See comment above for details.
ProphetOfDoom-PoD commentedon Aug 2, 2026
Reproduced on OpenCode Desktop 1.18.11, Windows.
A Coding session spawned a child task. The child stopped producing output with unfinished work, and the parent remained waiting for ~23 minutes. After I manually prompted/woke the session, it resumed immediately.
Parent: ses_0415de170ffeOSf3HG1C1570Bj
Child: ses_03f1c6683ffeKewZakW77xpJEc
Last child activity before stall: 2026-08-02T05:15:12Z
Resume activity: 2026-08-02T05:38:40Z
Separately, an unanswered assistant question only produced a response after I typed
wake up; it replied “I am awake and ready” instead of continuing the pending work.akanter commentedon Aug 28, 2026
I reproduced this on 1.18.23. Reverting to 1.18.21 fixed it though? (#45564)
thdxr commentedon Oct 11, 2026
Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (
opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.— from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊