When the OpenCode provider emits a permission.asked event with permission: "skill", T3 Code maps it to requestType: "unknown" and fails to render an approval UI. Because no prompt appears, the user cannot approve or deny the request, causing the thread to hang indefinitely. When the user manually clicks "Stop" to interrupt the stuck thread, OpenCode aborts the turn with MessageAbortedError: Aborted, but T3 Code retains pending_approval_count > 0 in state.sqlite, permanently corrupting the thread lifecycle into an un-settleable status ("This thread still needs attention").
Systematic Root Cause Analysis
- Permission Enforcement: OpenCode sessions inherit a permission rule array where
permission: "*" defaults to "action": "ask". Any tool or custom skill execution not explicitly set to "allow" triggers OpenCode to emit a permission.asked event.
- Event Ingestion & Mapping Failure: OpenCode emits
permission.asked with properties.permission: "skill". T3 Code catches this event in its provider reactor and translates it into a canonical request.opened event, but sets requestType: "unknown".
- UI Rendering Failure: Because
requestType is set to "unknown", the frontend UI lacks a renderer component for the approval bar or modal. The request stays open invisibly in the background.
- Indefinite Waiting State: Because the approval UI never appears, the thread hangs indefinitely waiting for input.
- Manual Abort & State Corruption: When the user notices the thread is stuck and clicks "Stop", OpenCode aborts the turn (
MessageAbortedError). However, the unrendered approval request remains unresolved in projection_pending_approvals with pending_approval_count = 1, blocking the manual "Settle" button in T3 Code.
Empirically Verified Log Evidence
From ~/.t3/userdata/logs/provider/<thread-id>.log:
1. OpenCode Session Permissions in Auto Mode (wildcard * set to "ask"):
"permission": [
{"permission": "*", "pattern": "*", "action": "ask"},
{"permission": "bash", "pattern": "*", "action": "ask"},
{"permission": "edit", "pattern": "*", "action": "ask"},
{"permission": "webfetch", "pattern": "*", "action": "ask"},
{"permission": "websearch", "pattern": "*", "action": "ask"},
{"permission": "codesearch", "pattern": "*", "action": "ask"},
{"permission": "external_directory", "pattern": "*", "action": "ask"},
{"permission": "doom_loop", "pattern": "*", "action": "ask"},
{"permission": "question", "pattern": "*", "action": "allow"}
]
2. OpenCode permission.asked & T3 Mapped request.opened (Event trace excerpt):
{
"eventId": "b698ae6b-0067-4b76-b267-a08ad0be827c",
"provider": "opencode",
"threadId": "<thread-uuid>",
"createdAt": "2026-07-28T22:18:02.779Z",
"turnId": "opencode-turn-d2f063e9-7889-441d-a330-6f431b4ceb26",
"requestId": "per_faace6c95001FLU3qz9EDUqZAy",
"raw": {
"source": "opencode.sdk.event",
"payload": {
"id": "evt_faace6c9500299RgttvutFy6Tz",
"type": "permission.asked",
"properties": {
"id": "per_faace6c95001FLU3qz9EDUqZAy",
"sessionID": "ses_05531a583ffeVPdtvIbMqV6d3P",
"permission": "skill",
"patterns": ["custom-skill"],
"metadata": {},
"always": ["custom-skill"],
"tool": {
"messageID": "msg_faace5c84001o4Md15M1xG8KR2",
"callID": "call-2b7a7472-415c-49e6-8c69-b90990cd91e8-0"
}
}
}
},
"type": "request.opened",
"payload": {
"requestType": "unknown",
"detail": "custom-skill",
"args": {}
},
"providerInstanceId": "opencode"
}
3. Manual Abort / Cancel (after waiting with no approval UI shown):
{
"type": "session.error",
"properties": {
"sessionID": "ses_05531a583ffeVPdtvIbMqV6d3P",
"error": {
"name": "MessageAbortedError",
"data": { "message": "Aborted" }
}
}
}
Log Comparison: Auto vs Auto-Accept Edits vs Full Access
Comparing raw provider logs across different runtime permission modes in T3 Code:
-
Auto / Auto-accept Edits:
T3 Code passes this permission array to OpenCode:
[{"permission": "*", "action": "ask"}, {"permission": "edit", "action": "ask"}, ...]
"Auto-accept edits" only auto-approves edit tools. Because the wildcard * policy remains "ask", any skill tool call still emits permission.asked, mapped to requestType: "unknown", and hangs.
-
Full Access:
T3 Code passes this permission array to OpenCode:
[{"permission": "*", "pattern": "*", "action": "allow"}]
The wildcard "action": "allow" auto-approves all tool executions backend-side, preventing OpenCode from emitting permission.asked and bypassing the unrendered approval bug entirely.
Steps to Reproduce
- Open T3 Code with the OpenCode provider active (e.g., using
opencode-go/grok-4.5 or opencode/claude-3-7-sonnet).
- Ensure mode is set to
Auto or Auto-accept edits (where permission: "*" action is "ask").
- Send a prompt asking the agent to run a skill tool (e.g.
"can you read the restoring agent sessions skill?").
- Observe that OpenCode requests permission for the
skill tool call, displaying Approval requested inside the Work Log, but T3 Code renders no interactive "Approve" / "Deny" buttons in the UI.
- The thread hangs indefinitely on
Working for ...s waiting for permission input.
- Click the red "Stop" button in the UI to interrupt the stuck thread.
- Attempt to click "Settle" on the thread; T3 Code rejects it with
"This thread still needs attention. Resolve or interrupt it first, then try again."
Expected Behavior
T3 Code should map permission: "skill" (and any unhandled permission types) to a valid approval request UI element (e.g. requestType: "skill_permission" or a generic fallback prompt) so the user can click "Approve" or "Deny", avoiding unhandled hanging requests and thread state deadlocks.
Workaround
Switch the thread execution mode in the bottom control bar from Auto to Full Access. This injects "action": "allow" for the wildcard policy, bypassing all permission events.
Relationship to #4561
This provides a concrete reproduction case for Issue #4561 ("Unable to settle stopped threads"). The un-settleable thread state is a direct symptom of unrendered approval requests remaining unresolved when a thread is manually stopped.
Environment
- OS: macOS
- App: T3 Code Nightly
- Backend Provider: OpenCode v1.18.9 (
opencode-go/grok-4.5)
- Verified Log Trace:
~/.t3/userdata/logs/provider/<thread-id>.log
When the OpenCode provider emits a
permission.askedevent withpermission: "skill", T3 Code maps it torequestType: "unknown"and fails to render an approval UI. Because no prompt appears, the user cannot approve or deny the request, causing the thread to hang indefinitely. When the user manually clicks "Stop" to interrupt the stuck thread, OpenCode aborts the turn withMessageAbortedError: Aborted, but T3 Code retainspending_approval_count > 0instate.sqlite, permanently corrupting the thread lifecycle into an un-settleable status ("This thread still needs attention").Systematic Root Cause Analysis
permission: "*"defaults to"action": "ask". Any tool or custom skill execution not explicitly set to"allow"triggers OpenCode to emit apermission.askedevent.permission.askedwithproperties.permission: "skill". T3 Code catches this event in its provider reactor and translates it into a canonicalrequest.openedevent, but setsrequestType: "unknown".requestTypeis set to"unknown", the frontend UI lacks a renderer component for the approval bar or modal. The request stays open invisibly in the background.MessageAbortedError). However, the unrendered approval request remains unresolved inprojection_pending_approvalswithpending_approval_count = 1, blocking the manual "Settle" button in T3 Code.Empirically Verified Log Evidence
From
~/.t3/userdata/logs/provider/<thread-id>.log:1. OpenCode Session Permissions in Auto Mode (wildcard
*set to"ask"):2. OpenCode
permission.asked& T3 Mappedrequest.opened(Event trace excerpt):{ "eventId": "b698ae6b-0067-4b76-b267-a08ad0be827c", "provider": "opencode", "threadId": "<thread-uuid>", "createdAt": "2026-07-28T22:18:02.779Z", "turnId": "opencode-turn-d2f063e9-7889-441d-a330-6f431b4ceb26", "requestId": "per_faace6c95001FLU3qz9EDUqZAy", "raw": { "source": "opencode.sdk.event", "payload": { "id": "evt_faace6c9500299RgttvutFy6Tz", "type": "permission.asked", "properties": { "id": "per_faace6c95001FLU3qz9EDUqZAy", "sessionID": "ses_05531a583ffeVPdtvIbMqV6d3P", "permission": "skill", "patterns": ["custom-skill"], "metadata": {}, "always": ["custom-skill"], "tool": { "messageID": "msg_faace5c84001o4Md15M1xG8KR2", "callID": "call-2b7a7472-415c-49e6-8c69-b90990cd91e8-0" } } } }, "type": "request.opened", "payload": { "requestType": "unknown", "detail": "custom-skill", "args": {} }, "providerInstanceId": "opencode" }3. Manual Abort / Cancel (after waiting with no approval UI shown):
{ "type": "session.error", "properties": { "sessionID": "ses_05531a583ffeVPdtvIbMqV6d3P", "error": { "name": "MessageAbortedError", "data": { "message": "Aborted" } } } }Log Comparison: Auto vs Auto-Accept Edits vs Full Access
Comparing raw provider logs across different runtime permission modes in T3 Code:
Auto / Auto-accept Edits:
T3 Code passes this permission array to OpenCode:
[{"permission": "*", "action": "ask"}, {"permission": "edit", "action": "ask"}, ...]"Auto-accept edits" only auto-approves
edittools. Because the wildcard*policy remains"ask", anyskilltool call still emitspermission.asked, mapped torequestType: "unknown", and hangs.Full Access:
T3 Code passes this permission array to OpenCode:
[{"permission": "*", "pattern": "*", "action": "allow"}]The wildcard
"action": "allow"auto-approves all tool executions backend-side, preventing OpenCode from emittingpermission.askedand bypassing the unrendered approval bug entirely.Steps to Reproduce
opencode-go/grok-4.5oropencode/claude-3-7-sonnet).AutoorAuto-accept edits(wherepermission: "*"action is"ask")."can you read the restoring agent sessions skill?").skilltool call, displayingApproval requestedinside the Work Log, but T3 Code renders no interactive "Approve" / "Deny" buttons in the UI.Working for ...swaiting for permission input."This thread still needs attention. Resolve or interrupt it first, then try again."Expected Behavior
T3 Code should map
permission: "skill"(and any unhandled permission types) to a valid approval request UI element (e.g.requestType: "skill_permission"or a generic fallback prompt) so the user can click "Approve" or "Deny", avoiding unhandled hanging requests and thread state deadlocks.Workaround
Switch the thread execution mode in the bottom control bar from
AutotoFull Access. This injects"action": "allow"for the wildcard policy, bypassing all permission events.Relationship to #4561
This provides a concrete reproduction case for Issue #4561 ("Unable to settle stopped threads"). The un-settleable thread state is a direct symptom of unrendered approval requests remaining unresolved when a thread is manually stopped.
Environment
opencode-go/grok-4.5)~/.t3/userdata/logs/provider/<thread-id>.log