anomalyco / anomalyco/opencode
v2: interrupting the shell tool while command creation is pending can orphan the process manager
@neriousy is already working on this.
Since Sep 13, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Description
Found while porting a V1 plugin to the V2 plugin API. My plugin wraps shell execution in a sandbox and needs session interruption to reach the wrapped process. Tracing V2's shell path, I found an ownership gap during Shell.create's ready wait. The analysis is against v2.0.2 (commit ea5ae232); the probe under Steps to reproduce is written to confirm or falsify the finding on a live install.
The chain, at commit ea5ae232:
Shell.createcaptures the service context and buildsrunForkWithfrom it (packages/core/src/shell.ts:129-130). In the pinnedeffect@4.0.0-rc.112,runForkWithcreates a fresh fiber with no link to the calling fiber or any scope unlessoptions.signalis passed (packages/effect/src/internal/effect.ts:5413-5437). Shell passes none.createforks the scoped process manager withrunForkand discards the returned handle (shell.ts:286-305). The caller then awaits a readiness deferred on its own (shell.ts:406-416).- Interrupting the caller removes the readiness waiter only (
packages/effect/src/Deferred.ts:173-186); the producer keeps running. The manager holds the process and stays alive awaitingcommand.done. - Cleanup does exist: pending-launch kill and acquired-process release in
packages/util/src/cross-spawn-spawner.ts:270-296and:419-452. Those finalizers belong to the manager's scope, and at this commit caller interruption has no path into it. Also noteacquireReleasemasks interruption during acquisition by default (internal/effect.ts:3971-3987) and the spawner keeps that default. - The command timeout is installed after spawn, registration, and the output-file open wait (
shell.ts:338-397), so it bounds the run but leaves the readiness interval open. Service teardown interrupts registered commands'donedeferreds (shell.ts:139-149) and reaps them at shutdown; cancellation needs its own path.
Net effect: a session interrupted while the shell tool is between spawn and shell ID can leave the manager and its process running, with no handle any cleanup path knows about. The window's practical duration and frequency still need a runtime measurement.
A fix would keep create's ownership cancellable until successful handoff, for example by passing an interruption signal into runForkWith or holding the acquisition inside the caller's scope until the shell ID exists, while bounding pending creation and cleanup and leaving intentional post-handoff background execution untouched.
Related: #40829 (abort waits for bash to finish naturally), #47350 (stdio held by a background process hangs the tool), and PR #42756 (exit/stdio completion). Those cover behavior after a shell ID exists or during a normal run. The gap above is the interval before the caller receives one.
Plugins
oc-arkive (local V1 plugin in development)
OpenCode version
v2.0.2 source (commit ea5ae232)
Steps to reproduce
The probe is designed so a negative result falsifies the finding:
- Start a shell command whose output-file open is blocked (or stub the spawner so readiness stays pending), with the timeout disabled.
- Once the process exists but before
shell.createreturns, interrupt only the calling tool/session. - Exclude natural exit, the command timeout, and service teardown as causes.
- Observe the manager fiber and the spawned process separately: if the gap is real, neither is terminated by the caller's interruption.
- Compare with interruption after the shell ID is returned, where job-based cancellation works.
Operating System
Linux (Ubuntu)
Terminal
n/a
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.