Skip to content

promptAsync message persisted but session loop never scheduled — async wake prompts to idle session silently dropped #32010

Closed as not planned

Description

@DevinByte

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:

  1. 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.
  2. Instance routing: the prompt was stored by one instance but the loop-scheduling event was observed (and ignored) by the other.
  3. 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

  1. 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.
  2. Startup/idle reconciliation sweep: on instance start and/or periodically, scan for sessions whose latest message is role=user with 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.)
  3. 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.
  4. 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

  1. github-actions commented on Jun 12, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    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.

  2. zswll2 commented on Jun 17, 2026

    @zswll2

    Additional Reproduction — oh-my-openagent 4.11.0 + opencode 1.17.7

    Confirming this issue persists on the latest versions:

    Environment:

    Reproduction:

    1. Launch a single background task from the parent session
    2. End assistant turn, wait for task completion
    3. Two <system-reminder> notifications are injected:
      • First: carries OMO_INTERNAL_NOREPLY (forceNoReply safety override — expected)
      • Second: no NOREPLY, shouldReply=true (allComplete=true) — should trigger agent turn
    4. No agent turn follows. Parent assistant remains silent.
    5. User manually sends any message → agent wakes up, sees the notifications in context, and proceeds normally.

    Observations:

    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=true auto-resume are broken. The parent agent can never automatically collect background_output() results without manual user intervention.

    Happy to provide session logs or run any diagnostic commands if helpful.

  3. zswll2 commented on Jun 17, 2026

    @zswll2

    Root Cause Pinpointed: ProviderModelNotFoundError swallowed by catchCause in promptAsync handler

    Following 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.promptAsync wraps SessionPrompt.prompt in a catchCause that logs the error but never re-throws, then unconditionally returns NoContent (HTTP 204):

    // SessionHttpApi.promptAsync handler (de-minified)
    yield* m.prompt({ ...payload, sessionID }).pipe(
      Effect.catchCause((cause) => Effect.gen(function* () {
        yield* Effect.logError("prompt_async failed", { sessionID, cause });  // logged only
        yield* Event.publish(Error, { sessionID, error: ... });               // internal event
      })),                                                                    // ← no Effect.fail!
      Effect.forkIn(scope, { startImmediately: true }),
      Effect.succeed(NoContent)                                               // ← always returns success
    );

    The SessionPrompt.prompt handler calls SessionPrompt.loop when noReply !== true:

    // SessionPrompt.prompt
    let z = yield* createUserMessage(a);    // message persisted (this part works)
    if (a.noReply === true) return z;
    return yield* SessionPrompt.loop({ sessionID: a.sessionID });  // ← this fails
    // SessionPrompt.loop
    yield* Engine.ensureRunning(sessionID, resolveMessages(sessionID), resolveModel(sessionID));
    //                                                              ^^^^^^^^^^^^^^^^^^^^^^^^
    //                                                              ProviderModelNotFoundError thrown here

    Log evidence (5 occurrences)

    level=ERROR message="prompt_async failed" cause=Cause([Die(ProviderModelNotFoundError)])
    

    All prompt_async invocations that should trigger a reply (noReply=false) fail with ProviderModelNotFoundError during 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

    1. oh-my-openagent side: sendParentWakePrompt → dispatchInternalPrompt → promptAsync receives HTTP 204 → treats as accepted. No retry, no error.
    2. opencode side: catchCause swallows 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.prompt path with a complete model payload and succeeds normally).

    Likely trigger

    The promptAsync request from oh-my-openagent's parent-wake may not include a model field 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 → resolveModel fails. The synchronous session.prompt path doesn't hit this because the TUI always sends the full model in the payload.

    Suggested fix (server-side)

    The catchCause in SessionHttpApi.promptAsync should not swallow errors unconditionally. At minimum, ProviderModelNotFoundError (and similar Die errors) should propagate or trigger a fallback model resolution rather than being silently dropped. Alternatively, SessionPrompt.loop should 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

  4. zswll2 commented on Jun 20, 2026

    @zswll2

    Closing — Root cause was environmental, not a code bug

    After further investigation, the promptAsync failed: ProviderModelNotFoundError errors were caused by multiple opencode server instances running simultaneously on the same machine:

    1. A TUI instance (interactive session)
    2. A headless opencode serve --port 4096 instance (running as systemd service)

    When oh-my-openagent's parent-wake mechanism called promptAsync for 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 in ProviderModelNotFoundError.

    Fix: Stopping the redundant opencode-serve.service systemd 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.promptAsync or SessionPrompt.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.

  5. zswll2 commented on Jun 20, 2026

    @zswll2

    Closing — environmental issue (multiple opencode instances), not a code bug. See comment above for details.

  6. ProphetOfDoom-PoD commented on Aug 2, 2026

    @ProphetOfDoom-PoD

    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.

  7. akanter commented on Aug 28, 2026

    @akanter

    I reproduced this on 1.18.23. Reverting to 1.18.21 fixed it though? (#45564)

  8. thdxr commented on Oct 11, 2026

    @thdxr
    Member

    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 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions