anomalyco / anomalyco/opencode

v2: interrupting the shell tool while command creation is pending can orphan the process manager

Open
#48,838 0 comments 0 reactions 1 assignee View on GitHub

@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:

  1. Shell.create captures the service context and builds runForkWith from it (packages/core/src/shell.ts:129-130). In the pinned effect@4.0.0-rc.112, runForkWith creates a fresh fiber with no link to the calling fiber or any scope unless options.signal is passed (packages/effect/src/internal/effect.ts:5413-5437). Shell passes none.
  2. create forks the scoped process manager with runFork and discards the returned handle (shell.ts:286-305). The caller then awaits a readiness deferred on its own (shell.ts:406-416).
  3. 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 awaiting command.done.
  4. Cleanup does exist: pending-launch kill and acquired-process release in packages/util/src/cross-spawn-spawner.ts:270-296 and :419-452. Those finalizers belong to the manager's scope, and at this commit caller interruption has no path into it. Also note acquireRelease masks interruption during acquisition by default (internal/effect.ts:3971-3987) and the spawner keeps that default.
  5. 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' done deferreds (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:

  1. Start a shell command whose output-file open is blocked (or stub the spawner so readiness stays pending), with the timeout disabled.
  2. Once the process exists but before shell.create returns, interrupt only the calling tool/session.
  3. Exclude natural exit, the command timeout, and service teardown as causes.
  4. Observe the manager fiber and the spawned process separately: if the gap is real, neither is terminated by the caller's interruption.
  5. Compare with interruption after the shell ID is returned, where job-based cancellation works.

Operating System

Linux (Ubuntu)

Terminal

n/a

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.