Skip to content

[Bug] T3 Code silently drops OpenCode skill permission requests — approval never shown, turn aborts #4795

Description

@Jandev9

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

  1. 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.
  2. 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".
  3. 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.
  4. Indefinite Waiting State: Because the approval UI never appears, the thread hangs indefinitely waiting for input.
  5. 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:

  1. 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.

  2. 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

  1. Open T3 Code with the OpenCode provider active (e.g., using opencode-go/grok-4.5 or opencode/claude-3-7-sonnet).
  2. Ensure mode is set to Auto or Auto-accept edits (where permission: "*" action is "ask").
  3. Send a prompt asking the agent to run a skill tool (e.g. "can you read the restoring agent sessions skill?").
  4. 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.
  5. The thread hangs indefinitely on Working for ...s waiting for permission input.
  6. Click the red "Stop" button in the UI to interrupt the stuck thread.
  7. 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

Metadata

Metadata

Assignees

No one assigned

    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