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
I searched existing issues and did not find a duplicate.
I included enough detail to reproduce or investigate the problem.
Area
apps/server
Summary
OpenCodeAdapter never settles its pending permission requests when a session tears down. Any approval still open when the session stops is stranded in projection_pending_approvals with status = 'pending' forever, so projection_threads.pending_approval_count stays above zero and the thread can never be settled:
This thread still needs attention. Resolve or interrupt it first, then try again.
There is nothing left to resolve. The session is stopped, the turns that requested the approvals are long dead, and no UI surface can clear them — the only way out is hand-editing state.sqlite.
This is the OpenCode counterpart of #5119 (ClaudeAdapter leaking pending user inputs on teardown), which was fixed. The same parity gap exists here for approvals: CursorAdapter and GrokAdapter both call settlePendingApprovalsAsCancelled on teardown, and OpenCodeAdapter has no equivalent.
It is made much easier to hit by #4795 — permissions that map to requestType: "unknown" render no approval UI, so the user never sees the request in the first place and the turn dies with it still open. But the leak is a separate defect: it strands any pending permission on teardown, and fixing the UI mapping alone would not clear the rows that are already stuck.
Steps to reproduce
Start a thread on the OpenCode provider in auto runtime mode.
Let the session end — the turn erroring out does it, and so does interrupting or a server restart.
Try to settle the thread → refused with the message above, with no pending approval visible anywhere in the UI.
Auto mode is what makes this reliably reproducible. The permission array T3 sends to OpenCode differs by runtime mode, and only full-access appends a catch-all allow:
// auto — ends here, so the leading `*: ask` governs every unlisted permission kind
[{"permission":"*","pattern":"*","action":"ask"}, ..., {"permission":"question","pattern":"*","action":"allow"}]
// full-access — same array plus a trailing catch-all
[..., {"permission":"question","pattern":"*","action":"allow"}, {"permission":"*","pattern":"*","action":"allow"}]
In my thread every one of the six permission requests was raised while in auto mode; after switching the same thread to full-access, OpenCode raised none at all.
Verify in ~/.t3/userdata/state.sqlite:
SELECT request_id, turn_id, status, created_at FROM projection_pending_approvals WHERE status ='pending';
SELECT thread_id, pending_approval_count FROM projection_threads;
Expected behavior
Tearing down an OpenCode session settles its pending permission requests — emitting request.resolved with a cancel decision for each, exactly as CursorAdapter and GrokAdapter do, and as ClaudeAdapter now does for user inputs after #5119. The projection count drops to zero and the thread becomes settleable.
Actual behavior
The rows stay pending forever. canSettle refuses while hasPendingApprovals is true, and no user action can clear it. The thread is permanently stuck in the inbox.
Impact
Blocks work completely
Version or commit
0.0.33 — T3 Code (Alpha), headless t3 serve under a systemd user unit
Environment
Ubuntu 24.04.4 LTS, aarch64, T3 Code 0.0.33 (headless server, systemd t3code.service), provider: OpenCode 1.18.18 (opencode-go/glm-5.3, agent build), runtime mode auto when the requests leaked (thread later switched to full-access). Thread driven from the mobile client.
State left behind after the session stopped — four approvals from two turns that died hours earlier, against a thread whose latest turn had already completed:
Both of those turns are in state: error in projection_turns, and the thread's latest turn completed successfully at 15:51 — so the thread looks idle and healthy in every way except for the counter.
The split between what surfaced and what leaked lines up exactly with mapPermissionToRequestType. All six requests came from the same thread in the same auto-mode window:
Requested
permission
Mapped requestType
UI shown
Outcome
14:21:01
bash
command_execution_approval
yes
answered acceptForSession in 4s
14:21:01
bash
command_execution_approval
yes
answered acceptForSession in 6s
14:21:18
task
unknown
no
stranded
14:31:00
grep
unknown
no
stranded
14:31:00
grep
unknown
no
stranded
14:31:00
grep
unknown
no
stranded
Worth noting for #4795, which is filed against permission: "skill": the gap is not specific to skills. task, grep, codesearch, webfetch, websearch, external_directory and doom_loop all fall through to unknown, and in auto mode OpenCode is configured to ask for every one of them.
Root cause
apps/server/src/provider/Layers/OpenCodeAdapter.ts — context.pendingPermissions is populated on permission.asked and drained only on permission.replied:
stopOpenCodeContext (~:541) — the single teardown path, reached from stopSession and stopAll — aborts the remote session and closes the scope, but never touches that map and never emits request.resolved:
Since request.resolved is what clears the projection row, the row outlives the session. context.pendingQuestions looks like it has the same exposure.
Adapter parity:
Adapter
Settles approvals on teardown
CursorAdapter.ts
✅ settlePendingApprovalsAsCancelled
GrokAdapter.ts
✅ settlePendingApprovalsAsCancelled
ClaudeAdapter.ts
✅ drains pendingApprovals in stopSessionInternal
OpenCodeAdapter.ts
❌
Two things worth considering alongside the adapter fix, since users who already hit this are stuck:
A recovery path for existing rows. Migration 025_CleanupInvalidProjectionPendingApprovals only deletes approvals with no backing approval.requested activity; these have one, so they survive it. I had to stop the service and clear them by hand (status='resolved', decision='cancel', then recompute pending_approval_count).
A server-side guard. An approval whose turn is terminal and whose session is stopped can never be answered, so arguably it should not hold hasPendingApprovals true regardless of which adapter leaked it.
Before submitting
Area
apps/server
Summary
OpenCodeAdapternever settles its pending permission requests when a session tears down. Any approval still open when the session stops is stranded inprojection_pending_approvalswithstatus = 'pending'forever, soprojection_threads.pending_approval_countstays above zero and the thread can never be settled:There is nothing left to resolve. The session is
stopped, the turns that requested the approvals are long dead, and no UI surface can clear them — the only way out is hand-editingstate.sqlite.This is the OpenCode counterpart of #5119 (
ClaudeAdapterleaking pending user inputs on teardown), which was fixed. The same parity gap exists here for approvals:CursorAdapterandGrokAdapterboth callsettlePendingApprovalsAsCancelledon teardown, andOpenCodeAdapterhas no equivalent.It is made much easier to hit by #4795 — permissions that map to
requestType: "unknown"render no approval UI, so the user never sees the request in the first place and the turn dies with it still open. But the leak is a separate defect: it strands any pending permission on teardown, and fixing the UI mapping alone would not clear the rows that are already stuck.Steps to reproduce
bash/read/edit—grepandtaskboth do it. In auto mode these are guaranteed to prompt (see the permission arrays below), and none of them render an approval UI ([Bug] T3 Code silently drops OpenCode skill permission requests — approval never shown, turn aborts #4795), so the request opens invisibly and is never answered.Auto mode is what makes this reliably reproducible. The permission array T3 sends to OpenCode differs by runtime mode, and only
full-accessappends a catch-all allow:In my thread every one of the six permission requests was raised while in auto mode; after switching the same thread to full-access, OpenCode raised none at all.
Verify in
~/.t3/userdata/state.sqlite:Expected behavior
Tearing down an OpenCode session settles its pending permission requests — emitting
request.resolvedwith acanceldecision for each, exactly asCursorAdapterandGrokAdapterdo, and asClaudeAdapternow does for user inputs after #5119. The projection count drops to zero and the thread becomes settleable.Actual behavior
The rows stay
pendingforever.canSettlerefuses whilehasPendingApprovalsis true, and no user action can clear it. The thread is permanently stuck in the inbox.Impact
Blocks work completely
Version or commit
0.0.33 — T3 Code (Alpha), headless
t3 serveunder a systemd user unitEnvironment
Ubuntu 24.04.4 LTS, aarch64, T3 Code 0.0.33 (headless server, systemd
t3code.service), provider: OpenCode 1.18.18 (opencode-go/glm-5.3, agentbuild), runtime modeautowhen the requests leaked (thread later switched tofull-access). Thread driven from the mobile client.Logs or stack traces
State left behind after the session stopped — four approvals from two turns that died hours earlier, against a thread whose latest turn had already completed:
Both of those turns are in
state: errorinprojection_turns, and the thread's latest turn completed successfully at 15:51 — so the thread looks idle and healthy in every way except for the counter.The split between what surfaced and what leaked lines up exactly with
mapPermissionToRequestType. All six requests came from the same thread in the same auto-mode window:permissionrequestTypebashcommand_execution_approvalacceptForSessionin 4sbashcommand_execution_approvalacceptForSessionin 6staskunknowngrepunknowngrepunknowngrepunknownWorth noting for #4795, which is filed against
permission: "skill": the gap is not specific to skills.task,grep,codesearch,webfetch,websearch,external_directoryanddoom_loopall fall through tounknown, and in auto mode OpenCode is configured to ask for every one of them.Root cause
apps/server/src/provider/Layers/OpenCodeAdapter.ts—context.pendingPermissionsis populated onpermission.askedand drained only onpermission.replied:stopOpenCodeContext(~:541) — the single teardown path, reached fromstopSessionandstopAll— aborts the remote session and closes the scope, but never touches that map and never emitsrequest.resolved:Since
request.resolvedis what clears the projection row, the row outlives the session.context.pendingQuestionslooks like it has the same exposure.Adapter parity:
CursorAdapter.tssettlePendingApprovalsAsCancelledGrokAdapter.tssettlePendingApprovalsAsCancelledClaudeAdapter.tspendingApprovalsinstopSessionInternalOpenCodeAdapter.tsTwo things worth considering alongside the adapter fix, since users who already hit this are stuck:
025_CleanupInvalidProjectionPendingApprovalsonly deletes approvals with no backingapproval.requestedactivity; these have one, so they survive it. I had to stop the service and clear them by hand (status='resolved', decision='cancel', then recomputepending_approval_count).hasPendingApprovalstrue regardless of which adapter leaked it.