Skip to content

OpenCode 2 plugin layer: shared-bridge kill on timeout, subagent misclassification, and smaller issues #646

Open

Description

@null-axiom

AFT: code references at v0.61.0 (packages/aft-bridge, packages/opencode-plugin). OpenCode 2, no daemon. Our sessions run many background subagents, all inheriting the lead's folder, so they share one bridge per root.

1. Two timeouts with no child output kill the shared bridge and every in-flight call

aft-bridge/src/bridge.ts:1183-1215 and handleTimeout (~:1979-2013): a request that times out when consecutiveTimeouts >= hangThreshold (default 2) and the child has produced no output since it was sent SIGKILLs the bridge; all pending requests fail with "bridge killed during sibling timeout". With subagents sharing the root's bridge, one slow call can cancel every running shell call across them. aft-bridge/src/command-timeouts.ts gives 60 s only to callgraph/trace/inspect/grep/glob/search/semantic_search; ast_search, ast_replace, outline, zoom (with call-graph annotations), import and safety get the 30 s default, so a slow ast_search over a large repo while the bridge is busy is the likely trigger. (We haven't seen a kill in our logs; the "no child activity" guard makes it rarer.)

Suggestion: add those commands to the long table, and on timeout reject only the timed-out request when others are pending. Related: #317 (closed) for Inspect.

2. A slow subagent lookup marks the session as primary for 30 s

opencode-plugin/src/subagent-detect.ts:117-140: a lookup that fails or takes over 1 s is cached as "primary" for 30 s. That changes shell behaviour (bash.ts:642) and skips the stop handler (v2-session-lifecycle.ts:253), so a stopped subagent's background tasks aren't killed. The V2 lookup (subagent-detect.ts:221-245) has no Effect.timeout (unlike session-directory.ts:225), and the losing race leaves the fiber running. Many subagents starting together makes 1 s easy to miss. Suggestion: timeout the lookup, cache failures as "unknown" and retry.

3. Tool output that is JSON with "success": false becomes a tool error

opencode-plugin/src/tools/definitions/v2.ts:190-209,429-430 treats any text result that parses as JSON with success:false as a failure envelope and throws its message, including read of a JSON fixture and curl … | jq output in shell. Suggestion: apply the check only to results the AFT engine produced.

4. Smaller items

  • Failed completion ack is never retried. runtime-consumer.ts:288-295 admits the wake, then sends bash_ack_completions; if that throws, the completion is held, and on re-route session-delivery.ts:70 sees it as already admitted and returns before acking. The task likely stays outstanding (blocks idle eviction, reappears in drains). admittedCompletions also grows without bound.
  • Unbounded per-session maps. v2-session-lifecycle.ts:36-52 (SessionWatches per session, dropped only on Location dispose), :96 (task kill closures for primary sessions), sync-watch-abort.ts:24, permissions.ts:143, session-delivery.ts:61, and sessionBgStates (bg-notifications.ts:1924) pinned by tasks whose completion never arrives.
  • Silent failures. server-runtime.mjs:219 wraps bootLocation in Effect.promise; a rejection becomes a defect swallowed by Effect.ignoreCause (comment :201-204), leaving AFT absent for that Location with no error. v2-session-lifecycle.ts:223 runs the event consumer in Effect.forever(pull…); if the stream ends, subagent-stop cleanup stops with no log.
  • Live shell progress never reaches OpenCode 2. bash.ts:737 calls metadata({output, description}) with flat keys, but definitions/v2.ts:335-343 forwards only update.metadata and update.title; same in bash_watch.ts:174,220,224. Each chunk also runs Effect.runPromise(progress({})) without a catch.
  • Overrides keyed by the main checkout. server-runtime.mjs:91,98 acquires the pool by location.project?.canonical, and location-lifecycle.ts:243-271 stores overrides under it, while bridges are keyed by the session's own directory (the worktree, after OpenCode 2: a session in a linked git worktree reads and runs commands in the main checkout #387). configByRoot.get(worktree) misses, so a session rooted in a linked worktree may lose storage_dir / bash_permissions overrides (not verified end to end).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions