Skip to content

One-shot run exits before plugin-backgrounded work completes; plugins cannot detect a headless client on the shared service #48683

Closed as duplicate

Description

@jamie-dit

Summary

A one-shot opencode run exits as soon as the parent turn ends, without waiting for
work that a plugin moved to the background during that turn. The result is
exit 0 with the delegated work silently absent from the output. The failure is
intermittent: a fast background job finishes before teardown and the output is
complete; a slow one is dropped.

Separately, nothing reaching a plugin's tool.hook("execute.before") distinguishes a
headless run client from an interactive one on the shared serve --service
process, so a plugin cannot avoid the problem by scoping itself.

Measured on opencode2 v0.0.0-beta-19059, Linux, 2026-09-13.

Reproduction

Isolated HOME/XDG_*, minimal config with one provider and one sub-agent.
A plugin that sets input.background = true on subagent calls in
ctx.tool.hook("execute.before", ...). Prompt: delegate once to a sub-agent whose
task takes ~60s (sleep 60; echo PROBE_TOKEN), then report the token.

exit wall token in stdout
opencode run --standalone, no plugin 0 74.3s yes
opencode run --standalone, plugin 0 10.7s no
opencode run (shared serve --service), plugin 0 9.7s no

The 10.7s output reads: "I launched the subagent (sessionID: …) to run the sleep
command and return the token. I'll wait for it to finish and then reply with its
exact result."
The process then exits. The child's completion is delivered by
ShellTool.notifyWhenDone / Session.synthetic → wake, but by then there is no
process to wake.

With a 5s delegated task the token does appear, which is what makes this
hard to notice.

What a plugin can and cannot see

Dumped from a diagnostic plugin during a live run:

execute.before payload : { tool, sessionID, agent, messageID, id, input }
ctx.session.get(...)   : { cost, id, location, projectID, time, title, tokens }
ctx.app                : { name: "cli", version: "0.0.0-beta-19059", channel: "beta" }

None identifies the client. The x-opencode-client header is built from
app.name and so is cli for every client. The only discriminator found is the
server process argv: run --standalone spawns a private serve --stdio --port 0,
while plain run uses the shared serve --service. A plugin can detect the first
(and we now do) but not the second, because on the shared service a headless run
and the TUI are the same process.

Expected

Either or both of:

  1. A one-shot run waits for jobs it started (background shells, background
    sub-agents) before exiting, or at minimum exits non-zero / prints a warning
    when work it launched is still pending.
  2. The plugin context or the execute.before payload carries something a plugin
    can use to tell a non-interactive one-shot client from an interactive one
    (e.g. a session/client kind on the session record, or on ctx).

Why it matters

Any plugin that backgrounds work by default (we ship two: one for shell commands,
one for sub-agent delegation, both scoped to top-level sessions) makes headless
automation lossy in exactly the situation nobody is watching. The --standalone
argv check covers the private-server case; the shared-service case has no
plugin-side fix.

Environment

  • opencode2 v0.0.0-beta-19059 (@opencode-ai/cli)
  • Linux, env -i isolated root, provider via LiteLLM proxy
  • Plugins involved: two small internal plugins that set input.background = true
    in ctx.tool.hook("execute.before", ...) for the shell and subagent tools
    respectively, scoped to top-level sessions via ctx.session.get(...).parentID.
    Happy to share a minimal reproduction plugin if useful.

Activity

  1. github-actions commented on Sep 12, 2026

    @github-actions
    Contributor

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

  2. opencodejr commented on Oct 28, 2026

    @opencodejr
    Contributor

    Thanks for the very detailed report and measurements! I think this is a duplicate of #48361. It reports the same problem: a one-shot opencode run exits once the parent turn ends, while work moved to the background (there by the model with background: true, here by your plugin) is still running. So that work is missing from the output. On the current dev branch, the non-interactive run still finishes on session.execution.succeeded without checking for pending background shells or subagents.

    Possible duplicates and related issues, ordered by likelihood:

    Your second point, letting a plugin tell a non-interactive one-shot client from an interactive one on the shared serve --service, is a separate request. You're right that nothing a plugin receives today identifies the client: not the execute.before payload, not ctx.session.get, not ctx.app, and not the x-opencode-client header. If you'd like it tracked on its own, even once run waits for background work, please say so here or open a feature request for it.

    The bot will close this issue as a duplicate in 1 day unless you reply explaining how your problem is different.

  3. opencodejr commented on Oct 29, 2026

    @opencodejr
    Contributor

    I'm closing this as a duplicate of #48361. It reports the same problem: a one-shot opencode run exits when the parent turn ends, while work moved to the background is still running, so that work is missing from the output. We haven't heard that your case is different. Please follow #48361 for updates, and #57418 for related work on waiting for background jobs.

    Your separate idea of giving plugins a way to tell a headless one-shot client from an interactive one isn't covered by #48361. If you'd still like it, please open a feature request for it. If your problem turns out to be different, reply here with the details and we can reopen this. Thanks again for the thorough report!

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions