anomalyco / anomalyco/opencode
One-shot `run` exits before plugin-backgrounded work completes; plugins cannot detect a headless client on the shared service
@jlongster is already working on this.
Since Sep 12, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- Avg merge
- 7h 2m
- Merged PRs (30d)
- 384
Description
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:
- A one-shot
runwaits 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. - The plugin context or the
execute.beforepayload 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 onctx).
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 -iisolated root, provider via LiteLLM proxy - Plugins involved: two small internal plugins that set
input.background = true
inctx.tool.hook("execute.before", ...)for theshellandsubagenttools
respectively, scoped to top-level sessions viactx.session.get(...).parentID.
Happy to share a minimal reproduction plugin if useful.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.