You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Permission prompt hangs when viewing a subagent #56907
Opening a subagent session hides its permission prompt. Auto-accept uses that same empty list, so the subagent stays blocked with nothing to approve or deny.
Plugins
None
OpenCode version
2.0.27 / current dev
Steps to reproduce
Start a subagent that needs a shell permission.
Open that subagent session.
Try to answer the prompt, or turn auto-accept on.
The prompt does not appear, and auto-accept does not reply.
Thanks for the clear report. I reproduced this on 2.0.27, and it still happens on the latest dev (501b15a).
Setup: shell permission set to ask, and the parent agent starts the general subagent, which runs echo hello-from-child.
Prompt mode: the parent view shows "Permission required". After opening the subagent session (↓, then Enter), the prompt is gone and the shell call just spins with nothing to approve or reject.
Auto-accept (session.permissions: "autoaccept"): if the subagent asks for permission while its session is open, the request is never approved and the subagent stays "Running".
The recording below shows the auto-accept case. The screenshots show the prompt on the parent view, the same request with no prompt in the subagent view, and the stuck auto-accept run. The script below reproduces both modes.
The likely cause is in packages/tui/src/routes/session/index.tsx: the permission list is empty whenever the viewed session has a parent, and both the prompt and auto-accept read that list. Your PR #56906 changes this. Related but different: #51815 (the request is never recorded as pending) and #52411 (v1, attaching from a second instance).
recording.mp4
Reproduction script (drive.ts)
// Reproduces anomalyco/opencode#56907: while a subagent (child) session is open,// its pending permission prompt is not shown, and auto-accept does not answer it.// default : prompt mode -> prompt is visible on the parent, hidden in the child view// AUTO=1 : autoaccept -> request raised while viewing the child is never approvedimport{Effect}from"effect"import{Llm,OpenCodeDriver}from"opencode-drive"constauto=!!process.env.AUTOexportdefaultOpenCodeDriver.use({
...(process.env.OC_DEV ? {opencode: {dev: process.env.OC_DEV}} : {}),tui: {recording: true,keypressOverlay: true},project: {git: true,files: {"README.md": "hello\n"}},// Drive's default config allows everything; make shell ask.config: {permissions: [{action: "*",resource: "*",effect: "allow"},{action: "shell",resource: "*",effect: "ask"},],}asany,
...(auto ? {tuiConfig: {session: {permissions: "autoaccept"}}asany} : {}),},({ ui, llm })=>Effect.gen(function*(){// Parent: spawn a subagent.yield*llm.queue(Llm.toolCall({index: 0,id: "call_sub",name: "subagent",input: {agent: "general",description: "Run a shell command",prompt: "Run `echo hello-from-child`"},}),Llm.finish("tool-calls"),)yield*ui.submit("Delegate a shell command to a subagent")yield*ui.waitFor("1 subagent")if(!auto){// Child asks for shell permission right away; parent view shows the prompt.yield*llm.queue(Llm.toolCall({index: 0,id: "call_shell",name: "shell",input: {command: "echo hello-from-child",description: "Echo"}}),Llm.finish("tool-calls"),)yield*ui.waitFor("Permission required")yield*ui.screenshot("1-parent-view-prompt-visible")}// Open the subagent session (down opens the picker, enter opens the child).yield*ui.press("down")yield*Effect.sleep(1000)yield*ui.press("return")yield*Effect.sleep(2000)if(auto){// Child asks for shell permission while its session is the one being viewed.yield*llm.queue(Llm.toolCall({index: 0,id: "call_shell",name: "shell",input: {command: "echo hello-from-child",description: "Echo"}}),Llm.finish("tool-calls"),)}yield*Effect.sleep(3000)yield*ui.screenshot("2-child-view")yield*Effect.sleep(5000)constframe=yield*ui.capture()consttext=JSON.stringify(frame)yield*ui.screenshot("3-child-view-later")yield*Effect.log(`permission prompt visible in child view: ${text.includes("Permission required")}; `+`command ran: ${text.includes("hello-from-child\\n")||/\$echohello-from-child/.test(text)}`,)}),)
Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.
Adding server-side evidence for the subagent permission-prompt hang (reported in #56907; PR #56906 proposes keeping prompts in subagent views). The behaviour I see is not only "the prompt is hidden in the subagent view" — the server never answers the child's permission endpoints with anything but 404, so even a client that polls the child session gets nothing, and the tool call can only end when the engine aborts it after 3600 s.
Summary
Permission requests raised inside a child session (subagent) are never surfaced to the client. Both permission endpoints of a child session answer 404 Not Found:
GET /api/session/<child>/permission — the pending-request list the client polls
POST /api/session/<child>/permission/<requestID>/reply — the reply
Consequently the tool call stays pending for exactly 3600 s and is then aborted by the engine with Tool execution interrupted (outcome="interrupted"), with no actionable message for the user.
Either (a) the child's permission request is surfaced to the user — directly, or forwarded to the parent session — and can be answered, or (b) if child-session approval is intentionally unsupported, the call must fail fast with an explicit error instead of hanging for 60 minutes.
A 404 from the reply endpoint is also ambiguous for the client: 404 currently means "no such pending request", but here the request is pending and simply is not reachable through that endpoint, so the caller cannot tell "wrong request id" from "request exists in another scope".
Actual Behavior
GET /api/session/<child>/permission → 404 — the child's pending requests are never listed.
POST /api/session/<child>/permission/<requestID>/reply → 404 — the reply never reaches the pending request.
The tool call remains pending for 3600 s and is then aborted by the engine.
In packages/server/src/handlers/permission.ts (v2.1.2) the two endpoints are scoped differently:
session.permission.list resolves the session first (sessionInfo(sessions, ctx.params.sessionID)) and only then calls permission.forSession(...) with instances.provide(session). For a child session the session lookup itself fails — in my log the child sessions also answer 404 for the plain GET /api/session/<id> (17 such responses) — so the handler returns "session not found" → 404 and the pending list is never consulted.
session.permission.reply goes through requireOwnedRequest(...), which calls permission.get(requestID)without providing the location instance, and converts a miss into PermissionNotFoundError (→ 404) when the request is not in that instance's in-memory pending map, or when its sessionID does not match the URL.
Permission is a location-scoped node while SessionStore / PermissionSaved are global nodes, so the two endpoints resolve their dependencies differently.
Net effect for a child session: the client can neither list the pending requests nor apply a reply to one, so the request stays unanswered until the engine aborts the call — the 3600 s timeout is the only exit.
Additional Context
Scope is the session type, not the tool. The same ask rule is answered normally in the parent session (approving it stores a saved/"always allow" row); in a child session it never is. The hang also covers shell and external_directory faces, not only glob.
Observed volume. In a local database sweep over 1391 child sessions I counted 47 aborted tool parts in 31 sessions; 36 of them ran longer than 3000 s, i.e. they hit the same timeout. Face distribution of those 47: shell 24, glob 12, workspace-external read/grep 9, subagent 1, question 1. (The question case is a different class of hang — the tool itself waits for an answer, not a permission decision; question is not even exposed to subagents in the current tool set.)
In my log this yields only 404 — 93 permission-path requests (90 × reply, 3 × list) and never a 2xx.
The plain session endpoint fails the same way for those child sessions:
Workaround on the user side: avoid ask rules inside subagents (use allow/deny), so the 60-minute hang cannot be triggered. I am not aware of a server-side workaround.
Activity
opencode-agent commentedon Oct 19, 2026
Thanks for the clear report. I reproduced this on 2.0.27, and it still happens on the latest
dev(501b15a).Setup: shell permission set to
ask, and the parent agent starts thegeneralsubagent, which runsecho hello-from-child.session.permissions: "autoaccept"): if the subagent asks for permission while its session is open, the request is never approved and the subagent stays "Running".The recording below shows the auto-accept case. The screenshots show the prompt on the parent view, the same request with no prompt in the subagent view, and the stuck auto-accept run. The script below reproduces both modes.
The likely cause is in
packages/tui/src/routes/session/index.tsx: the permission list is empty whenever the viewed session has a parent, and both the prompt and auto-accept read that list. Your PR #56906 changes this. Related but different: #51815 (the request is never recorded as pending) and #52411 (v1, attaching from a second instance).recording.mp4
Reproduction script (drive.ts)
Run it with opencode-drive:
opencode-drive run ./drive.ts. It drivesopencodefrom yourPATH; setOC_DEVto the path of an OpenCode checkout to run that from source instead.liaochzh commentedon Oct 23, 2026
Adding server-side evidence for the subagent permission-prompt hang (reported in #56907; PR #56906 proposes keeping prompts in subagent views). The behaviour I see is not only "the prompt is hidden in the subagent view" — the server never answers the child's permission endpoints with anything but 404, so even a client that polls the child session gets nothing, and the tool call can only end when the engine aborts it after 3600 s.
Summary
Permission requests raised inside a child session (subagent) are never surfaced to the client. Both permission endpoints of a child session answer
404 Not Found:GET /api/session/<child>/permission— the pending-request list the client pollsPOST /api/session/<child>/permission/<requestID>/reply— the replyConsequently the tool call stays pending for exactly 3600 s and is then aborted by the engine with
Tool execution interrupted(outcome="interrupted"), with no actionable message for the user.Environment
TERM=xterm-256color(TERM_PROGRAM/COLORTERMunset)/bin/zshlatest(binary at~/.opencode/bin/opencode)pluginkey in~/.config/opencode/opencode.jsonc, no~/.config/opencode/plugin/orplugins/directory)Reproduction
~/.config/opencode/opencode.jsonc:{ "action": "glob", "resource": "**/*.md", "effect": "ask" }globcall is enough).404:Expected Behavior
Either (a) the child's permission request is surfaced to the user — directly, or forwarded to the parent session — and can be answered, or (b) if child-session approval is intentionally unsupported, the call must fail fast with an explicit error instead of hanging for 60 minutes.
A
404from the reply endpoint is also ambiguous for the client:404currently means "no such pending request", but here the request is pending and simply is not reachable through that endpoint, so the caller cannot tell "wrong request id" from "request exists in another scope".Actual Behavior
GET /api/session/<child>/permission→404— the child's pending requests are never listed.POST /api/session/<child>/permission/<requestID>/reply→404— the reply never reaches the pending request.In
packages/server/src/handlers/permission.ts(v2.1.2) the two endpoints are scoped differently:session.permission.listresolves the session first (sessionInfo(sessions, ctx.params.sessionID)) and only then callspermission.forSession(...)withinstances.provide(session). For a child session the session lookup itself fails — in my log the child sessions also answer404for the plainGET /api/session/<id>(17 such responses) — so the handler returns "session not found" →404and the pending list is never consulted.session.permission.replygoes throughrequireOwnedRequest(...), which callspermission.get(requestID)without providing the location instance, and converts a miss intoPermissionNotFoundError(→404) when the request is not in that instance's in-memorypendingmap, or when itssessionIDdoes not match the URL.Permissionis a location-scoped node whileSessionStore/PermissionSavedare global nodes, so the two endpoints resolve their dependencies differently.Net effect for a child session: the client can neither list the pending requests nor apply a reply to one, so the request stays unanswered until the engine aborts the call — the 3600 s timeout is the only exit.
Additional Context
askrule is answered normally in the parent session (approving it stores a saved/"always allow" row); in a child session it never is. The hang also coversshellandexternal_directoryfaces, not onlyglob.shell24,glob12, workspace-externalread/grep9,subagent1,question1. (Thequestioncase is a different class of hang — the tool itself waits for an answer, not a permission decision;questionis not even exposed to subagents in the current tool set.)404— 93 permission-path requests (90 ×reply, 3 × list) and never a2xx.The plain session endpoint fails the same way for those child sessions:
external_directoryrequest), desktop: permission dialog stuck after tool interrupt/timeout; Deny/Allow replies 404 forever, composer blocked #55678 (permission dialog stuck after interrupt/timeout; replies404), permissions: ask effect is fail-open for shell commands (command runs, client reply gets 404) #56470 (askfail-open for shell; client reply404). What I could not find covered elsewhere is the server-side endpoint behaviour (list and reply both404, with the location/global scope asymmetry) and the exact 3600 s abort.askrules inside subagents (useallow/deny), so the 60-minute hang cannot be triggered. I am not aware of a server-side workaround.