Skip to content

fix(opencode): surface child-session activity and approvals in parent thread - #7840

Open
sabraman wants to merge 1 commit into
pingdotgg:mainfrom
sabraman:fix/opencode-child-session-approvals
Open

fix(opencode): surface child-session activity and approvals in parent thread#7840
sabraman wants to merge 1 commit into
pingdotgg:mainfrom
sabraman:fix/opencode-child-session-approvals

Conversation

@sabraman

@sabraman sabraman commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Problem

When an OpenCode Task-tool child session requested a permission — for example external_directory to read a sibling project — the adapter dropped every event whose session id differed from the parent's. The child sat waiting on an approval nobody could see: OpenCode's REST API reported the child busy with a pending permission, projection_pending_approvals had no row for the thread, and the T3 thread showed as running forever with no way forward. Reproduced on T3 Code nightly 0.0.34 with OpenCode 1.18.19.

What this does

  • Tracks child sessions. Unknown event session ids are resolved by walking the session's parentID chain (with per-hop directories and an ignore-list for unrelated sessions), so child Task activity flows into the parent thread carrying real provider session refs.
  • Makes child approvals visible. Child permission.asked/question.asked render as regular approvals. Permission kinds the canonical map doesn't know (external_directory, task, webfetch, …) get a generic approval card that preserves the raw permission detail and args instead of disappearing. Replies go back to the exact child request using its real request ID and directory.
  • Keeps lifecycle honest. The parent turn stays open while any child is active; pending approvals/questions settle (and child tasks complete) on abort, root error, unexpected exit, interrupt, and teardown instead of stranding the thread as permanently unsettleable.
  • Preserves security semantics on children. Each registered child gets the thread's runtime-mode ruleset re-asserted layered under whatever OpenCode derived at create time — last-match-wins keeps inherited allows (e.g. an already-approved external_directory) and agent-level denies authoritative while the baseline covers everything else. Full-access stays full-access, supervised stays supervised, nothing is auto-allowed. This also makes behavior independent of upstream permission-inheritance bugs (Subagent loses inherited external_directory allow when spawned via task tool anomalyco/opencode#30527 / #30529).
  • Keeps subagent narration out of the parent transcript. Results surface through task rows; same call the Claude adapter made.

Evidence

Before/after screenshots of the invisible-vs-visible child approval will be attached shortly.

Tests

88 adapter + ingestion tests pass, including new coverage for: child approval → visible parent approval + reply routed to the child directory; inherited-rule layering; root turn completing once its last pending approval resolves after idle; child narration suppression; teardown settling pending approvals; full-access inheritance; supervised prompts staying enforced; and child runtime errors leaving parent session state alone.


Note

High Risk
Touches OpenCode session lifecycle, permission replies, and parent-thread error/turn completion. Bugs here can strand approvals, mis-route replies, or leave threads running or in error.

Overview
OpenCode Task-tool child sessions are no longer dropped when their session id differs from the parent. The adapter walks the parentID chain, tracks children, and emits task.* rows plus providerRefs so activity and approvals land on the parent thread.

Child permission prompts (including unmapped kinds like external_directory) show as generic Permission approval cards with raw detail. Replies go to the child’s directory. Pending requests settle on abort, error, idle, or teardown. The parent turn stays open while children run, and only completes when the root is idle with no active children or pending prompts. Child runtime.error is recorded as activity and does not flip the parent session to error.

Child permission rules layer the thread’s runtime-mode baseline under OpenCode’s inherited rules (last-match-wins). Subagent narration stays out of the parent transcript. Web and mobile treat requestKind: "unknown" as a first-class pending approval.

Reviewed by Cursor Bugbot for commit cbd40e7. Bugbot is set up for automated code reviews on this repo. Configure here.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 19e85aeb-24aa-4021-b73d-b375c8124f64

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 21, 2026

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding: the new child-session suppression in ProviderRuntimeIngestion changes backend behavior without a focused test. Everything else in the changed Effect code (namespace imports from effect/* subpaths, runOpenCodeSdk/toRequestError error mapping, Effect.catch on fully handled channels, no new runtimes or service-instance injection) matches the conventions.

Posted via Macroscope — Effect Service Conventions

: activeTurnId === null || eventTurnId === undefined || sameId(activeTurnId, eventTurnId);

if (shouldApplyRuntimeError) {
if (shouldApplyRuntimeError && event.providerRefs?.providerParentSessionId === undefined) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This guard silently changes ingestion behavior: a runtime.error originating from a child session no longer moves the thread session into error with lastError, while the activity is still recorded. Since backend behavior changed, consider adding a focused ingestion test — the suite currently covers only the parent-session case ("maps runtime.error into errored session state") and has no event carrying providerRefs.providerParentSessionId, so this branch is untested and a regression would go unnoticed.

Posted via Macroscope — Effect Service Conventions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 72d7d4d — focused ingestion test: a runtime.error carrying providerParentSessionId records the activity but leaves parent session state untouched (keeps parent session state when a child-session runtime.error arrives).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts
Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts
Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts
@macroscopeapp

macroscopeapp Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces substantial new capability for child/nested session management in the OpenCode adapter (~1500 lines). Multiple unresolved high-severity findings identify potential bugs in permission synchronization logic that could overwrite inherited permissions or permanently drop nested child events.

Not approved because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@sabraman
sabraman force-pushed the fix/opencode-child-session-approvals branch from d528006 to 350c5ad Compare August 21, 2026 20:28
Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts Outdated
Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts
Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts
Comment on lines +879 to +883
const probe = yield* loadOpenCodeSession(context, child.sessionId, child.directory);
const existing =
probe.kind === "found"
? ((probe.info as { readonly permission?: PermissionRuleset }).permission ?? [])
: [];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High Layers/OpenCodeAdapter.ts:879

When session.get returns unavailable (or missing), synchronizeChildPermissions updates the child with only the runtime baseline, erasing inherited or agent-specific restrictions; in full-access mode this can turn a restricted child into */* allow. Return without updating unless the probe is found, so existing permissions are never overwritten without first loading them.

       const probe = yield* loadOpenCodeSession(context, child.sessionId, child.directory);
-      const existing =
-        probe.kind === "found"
-          ? ((probe.info as { readonly permission?: PermissionRuleset }).permission ?? [])
-          : [];
+      if (probe.kind !== "found") {
+        return;
+      }
+      const existing = (probe.info as { readonly permission?: PermissionRuleset }).permission ?? [];
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/OpenCodeAdapter.ts around lines 879-883:

When `session.get` returns `unavailable` (or `missing`), `synchronizeChildPermissions` updates the child with only the runtime baseline, erasing inherited or agent-specific restrictions; in `full-access` mode this can turn a restricted child into `*`/`*` allow. Return without updating unless the probe is `found`, so existing permissions are never overwritten without first loading them.

if (parentProbe.kind === "missing") {
context.ignoredSessionIds.add(sessionId);
return undefined;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parent probe blacklists on directory miss

High Severity

The leaf-session probe retries session.get without a directory after a scoped miss, but the parentID walk only tries a scoped lookup and treats a directory-scoped 404 as a confirmed miss. That permanently adds the leaf id to ignoredSessionIds, so nested child events (including approvals) stay dropped for the rest of the session.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 72d7d4d. Configure here.

context.client.session.update({
sessionID: child.sessionId,
directory: child.directory,
permission: merged,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failed permission probe overwrites rules

High Severity

When loading a child's existing ruleset fails (unavailable or non-found), synchronizeChildPermissions still writes the runtime-mode baseline alone and sets permissionsSynchronized. That permanently drops OpenCode-derived inherited allows and denies instead of retrying the merge on a later event.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 72d7d4d. Configure here.

… thread

A Task-tool child session's permission.asked events were filtered out
because the adapter dropped every event whose session id differed from
the parent's, so external_directory prompts hung invisibly and the
thread looked idle forever.

Track child sessions via parentID chain resolution, route their events
to the parent thread with real child session refs, map unmapped
permission kinds to a visible generic approval carrying raw permission
detail, reply against the child session directory, keep the turn open
while children run, settle pending permissions/questions on abort,
error, exit, and teardown, and re-assert the T3 runtime-mode ruleset on
each child at registration layered under OpenCode's derived rules so
inherited allows and plan/subagent denies stay authoritative while
full-access vs supervised behavior no longer depends on upstream
inheritance (#30527/#30529). Subagent narration stays out of the parent
transcript, mirroring the Claude adapter.
@sabraman
sabraman force-pushed the fix/opencode-child-session-approvals branch from 72d7d4d to cbd40e7 Compare August 21, 2026 21:48

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit cbd40e7. Configure here.

raw,
child.failure ? "failed" : "completed",
child.failure ?? "OpenCode child session completed.",
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Child idle cancels open approvals

High Severity

handleOpenCodeSessionIdle always runs settlePendingOpenCodeRequests for child sessions before completing the child task. Root idle deliberately leaves pending approvals open so the turn can finish after the user replies, and this PR’s own root regression relies on idle-while-pending. The same OpenCode interleaving on a child auto-rejects the approval and marks the task done, so child permission prompts can disappear before anyone can answer.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit cbd40e7. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant