Hidden runs: create OpenCode 2 hidden children unparented (issue 913)

Merged
NE
3h ago
+900-315

Approved issue: #913

On OpenCode 2.3 every historian and dreamer run starts a turn in the user's session. The hidden child was created with parentID set to the user's session, and OpenCode 2.3 reports every idle of a parented session to its parent and wakes it. Each hidden run therefore lands as a <subagent> completion message in the user's session and prompts the main agent to answer it.

This change creates the hidden child as a root session of the run's directory (childCreateInput already carries that location) and keeps removing it when the run ends. The parent read-back, the dropped-parent notice and the directory refusal only existed for the parented shape, so they go with it.

Evidence

tests/opencode2/hidden-child-native-remove.test.ts now also checks that the user session's message ids are unchanged after a historian and a dreamer run, and asserts parentID === null for both children.

HostPluginResult
OpenCode 2.3.0master 82b8c13fail: user session gains 6 messages; both children have parentID = user session
OpenCode 2.3.0this branchpass: no new user-session messages; both children parentID: null, removed after the run
OpenCode 2.0.22 (pinned)master 82b8c13fail: parentID = user session
OpenCode 2.0.22 (pinned)this branchpass

The 2.3.0 rows used MC_E2E_OPENCODE2_CLI with @opencode/cli@2.3.0 and MC_E2E_OPENCODE2_AI_ROOT with @opencode/ai@2.3.0, plus a local, uncommitted relaxation of schemaGeneration to accept v2.3.x (the e2e schema guard has no 2.3 bundle yet). That relaxation is not part of this PR.

Also run on Linux (WSL Ubuntu 22.04, Bun 1.4.2): bun run typecheck exit 0; bun run lint exit 0 (no findings in changed files); biome format on changed files: no fixes; src/v2/hidden-child-native.test.ts + src/v2/hidden-completion.test.ts: 88 pass / 0 fail.

Not run: the full unit and e2e suites.

Harness coverage (required)

  • OpenCode 1: not applicable — surface does not exist there (the OpenCode 1 host does not report a child's idle to its parent; this lifecycle is src/v2 only)
  • OpenCode 2: covered
  • Pi: not applicable — surface does not exist there (no host session parenting for hidden runs)
  • OMP: not applicable — surface does not exist there (no host session parenting for hidden runs)
  • Rust module: not applicable — surface does not exist there (its parent_id is a historian invocation id, not a host session)

Summary by cubic

Fixes issue #913: historian and dreamer runs on OpenCode 2.3 no longer inject <subagent> turns into the user's session. Hidden children are now created as root sessions of the run's directory instead of being parented to the user's session, and are still removed when the run ends.

Since a root session has no parent to clean it up after a crash or failed removal, each child is recorded in context.db and the record is cleared once the session is removed. A startup sweep and a 30-minute interval remove records past the run's deadline plus a grace period, so another instance's live run is never touched. keep_subagents children are not recorded, and a run whose record cannot be written is refused and its session removed at once.

  • Each cleanup record stores the run's deadline and any retry extensions, so long-running retries are never swept early.
  • Live in-process children are skipped by the sweep; failed removals release that protection so the next pass can retry them.
  • Each sweep pass is bounded to 50 removals or 30 seconds, tries never-attempted records before failed ones (oldest failure first), and stops between records when the plugin is disposed.
  • The parent read-back, dropped-parent notice, and directory refusal only existed for the parented shape, so they were removed.
  • E2E tests now accept OpenCode 2.3, assert unparented children with unchanged user-session messages, and verify a run interrupted by a restart is removed by the next startup sweep.

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; both numbered findings are fixed and no new actionable issues were found.

Summary

Hidden runs now create root sessions without waking the user's session. Durable records let later sweeps retry cleanup after a failed removal or restart.

The latest changes address both numbered findings:

  • Hidden historian and dreamer runs use root sessions, not children of the user’s session.
  • The plugin records unfinished hidden sessions and removes them after they expire.

No new actionable issues were found. Checks were based on source inspection; tests were not run.

Reviews (4) · Last reviewed commit: "mason: order hidden-child sweeps past fa..." · Reviewed by Greptile