Skip to content

Sub-sub-agents (depth: 2) "ask" permission doesn't surface to user and stalls #39112

Closed as not planned

Description

@mattew113

Description

When configuring OpenCode settings "subagent_depth" to 2, allowing subagents to deploy subagents at one level below, if a cascaded subagent tries to access any permission that is configured to "ask", the permission request never surfaces up to the user and the session stalls.

This can be easily tested with native "general" subagent by giving them access to deploy "explore" subagents.

Plugins

No response

OpenCode version

1.18.7

Steps to reproduce

  1. Setup your opencode.json settings with subagent depth 2 and grant a subagent like general with task access to deploy explore subagents:

{ "$schema": "https://opencode.ai/config.json", "subagent_depth": 2, "agent": { "general": { "tools": { "task": true }, "permission": { "task": { "*": "deny", "explore": "allow" } } } } }

  1. Deploy a @general subagent asking to deploy a explore subagent to access any permission that would require "ask", e.g. external_directory access.

  2. Sub-sub-agent permission request never surface to user and session stalls.

Screenshot and/or share link

Image Image Image Image

Operating System

Ubuntu 24.04

Terminal

SSH over WSL

Activity

  1. github-actions commented on Jul 27, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    If your issue is distinct (e.g. specifically triggered by subagent_depth: 2 config or the two-level nesting case), please clarify how it differs from the above.

  2. balerdis commented on Sep 16, 2026

    @balerdis

    Still reproduces on 1.18.31 (this was filed against 1.18.7). Source reading below in case it saves someone the trip.

    Where it breaks

    packages/tui/src/routes/session/index.tsx:

    const children = createMemo(() => {
      const parentID = session()?.parentID ?? session()?.id
      return sync.data.session
        .filter((x) => x.parentID === parentID || x.id === parentID)
        .toSorted((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
    })
    const permissions = createMemo(() => {
      if (session()?.parentID) return []
      return children().flatMap((x) => sync.data.permission[x.id] ?? [])
    })

    Two defects that compound:

    1. children() collects direct children only — one level, not a recursive descendant walk. Viewed from the root it holds level-1 sessions; a level-2 session id is never in that list, so its entry in sync.data.permission[...] is never read.
    2. permissions() returns [] whenever session()?.parentID is truthy, i.e. from any non-root view — so navigating into the subagent view does not help either.

    Net effect: a permission raised at depth 2 is unreachable from every view. Depth 1 works only because a level-1 session happens to be a direct child of the root.

    The request itself is fine. packages/tui/src/context/sync.tsx stores it correctly under its own session id (setStore("permission", request.sessionID, [request])), and the event is broadcast per directory/workspace with no session filter. It is purely the render layer that drops it.

    Worth noting that PermissionV1.Request (packages/schema/src/v1/permission.ts) carries only sessionID — no parentID or rootID — so the view has nothing to walk up with even if it wanted to.

    It hangs rather than failing

    packages/opencode/src/permission/index.ts blocks on Deferred.await(deferred) with no timeout anywhere in that file. The only thing that ever resolves it is the instance-state finalizer on teardown. So the call does not fail, it hangs until the session is killed — which is why blocked (1) in the status bar is the only visible symptom.

    Please don't fix only permissions()

    Three lines below, questions() has the identical short-circuit and the same one-level children():

    const questions = createMemo(() => {
      if (session()?.parentID) return []
      return children().flatMap((x) => sync.data.question[x.id] ?? [])
    })

    #30635 was closed having fixed a single level of nesting, which is why this issue, #43996 and #44747 are all still open. A complete fix needs three things together: make children() walk descendants recursively, drop the non-root bail-out, and cover questions() in the same patch. I filed the questions() half as #49342 so it stays trackable — it is the same root cause, not a separate bug.

    Our repro

    Three levels, external_directory left at the default ask: an orchestrator agent delegates to a general-purpose subagent, which delegates to a read-only explorer subagent. The explorer globs a directory outside the worktree. The status bar shows blocked (1), no dialog renders in any view, and the turn never ends. Our configuration ships subagent_depth: 10, so depth-2 chains are routine rather than exotic — which is probably why we hit this on day one of using them.

    As a workaround we have had to set external_directory to allow with an explicit deny floor over sensitive directories, because the alternative is pre-granting every external path a user might ever touch. We would much rather go back to ask.

    One detail for whoever writes the fix: external_directory asks about path.join(path.dirname(target), "*") (packages/opencode/src/tool/external-directory.ts), never the file path — so a deny floor can only ever be directory-granular. That is fine, but it surprised us, and it may be worth a line in the permission docs.

  3. thdxr commented on Oct 11, 2026

    @thdxr
    Member

    Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.

    — from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions