anomalyco / anomalyco/opencode

run --format json drops every subagent part: the loop tracks child sessions, honours them for permissions, and filters parts against the root session

Open
#49,300 1 comment 0 reactions 1 assignee View on GitHub

@rekram1-node is already working on this.

Since Sep 16, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

Summary

In packages/opencode/src/cli/cmd/run.ts (v1.18.30), the event loop deliberately maintains a set of child sessions, uses that set when handling permissions, and then ignores it when handling parts — so --format json emits nothing a subagent does.

const sessions = new Set([sessionID])                                                  // :699

if (event.type === "session.created" && event.properties.info.parentID) {
  if (sessions.has(event.properties.info.parentID)) sessions.add(event.properties.info.id)   // :704
}

if (event.type === "message.part.updated") {
  const part = event.properties.part
  if (part.sessionID !== sessionID) continue                                           // :722  <-- root only
  ...
}

if (event.type === "permission.asked") {
  const permission = event.properties
  if (!sessions.has(permission.sessionID)) continue                                    // :803  <-- set honoured
}

:704 exists only to populate sessions, and :803 is its only consumer. If subagent parts were meant to be filtered out, :704 would not need to run at all — the two lines read as though :722 was written before the set existed and never updated.

Effect

A task subagent's reasoning, tool calls and tool results never reach stdout in JSON mode. The parent's own task tool part is still emitted when it completes, so a consumer sees that a subagent ran and its final task_result, but nothing of how it got there. Interactive mode is unaffected in the same way (the same filter applies, it just matters less when a human is watching).

The data is not lost — it is in the session store with the correct parent_id — so this is purely the JSON stream dropping something the process already has and already tracked.

Why it matters beyond cosmetics

--format json is the integration surface. Anything driving opencode programmatically (we front it through an OpenAI-compatible bridge) can persist only what crosses this stream. In our case an agent delegated the independent verification of a research result to a task subagent; the subagent's 152-step working record exists only in opencode.db, and every downstream record of that run shows a half-hour gap where its most important work happened.

Suggested fix

-              if (part.sessionID !== sessionID) continue
+              if (!sessions.has(part.sessionID)) continue

and stamp the originating session on emitted events so a consumer can attribute them, since emit currently writes the root sessionID unconditionally:

function emit(type: string, data: Record<string, unknown>) {
  if (args.format === "json") {
    process.stdout.write(JSON.stringify({ type, timestamp: Date.now(), sessionID, ...data }) + EOL)

Passing the part's own sessionID (with the root still available as, say, rootSessionID) would let a consumer rebuild the tree that parentID already describes.

Two things worth a maintainer's judgement, which is why this is an issue rather than a PR: whether subagent parts should be on by default or behind a flag (they are verbose, and some consumers will assume one session per stream), and whether --thinking should gate subagent reasoning the way it gates the root's. I have not sent a patch because I cannot exercise the interactive path or your test suite here, and a behaviour change on the default output of run deserves your call on the shape rather than mine.

Happy to send a PR in whatever shape you prefer.

Environment

opencode 1.18.30, macOS 15 (Darwin 25.1.0), opencode run --format json driven non-interactively over stdin.

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.