Skip to content

[Bug]: OpenCodeAdapter leaks pending permission requests on session teardown — thread becomes permanently unsettleable #7113

Closed

Description

@simonechecchia

Before submitting

  • 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

  1. Start a thread on the OpenCode provider in auto runtime mode.
  2. Get the agent to use a tool whose permission kind is outside bash / read / edit — grep and task both 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.
  3. Let the session end — the turn erroring out does it, and so does interrupting or a server restart.
  4. 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.

Logs or stack traces

# ~/.t3/userdata/logs/provider/events.<thread-id>.log — request opened, never resolved
[2026-08-15T14:31:00.040Z] NTIVE: {"type":"permission.asked","turnId":"opencode-turn-44264957-...","payload":{"properties":{
  "id":"per_005d54cc0001f1q32zDJCj2PCI","sessionID":"ses_<redacted>",
  "permission":"grep","patterns":["<search-term>"],"metadata":{"pattern":"<search-term>","include":"*.vue"}}}}

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:

projection_thread_sessions: status=stopped, active_turn_id=null
projection_threads:         pending_approval_count=4, pending_user_input_count=0

request_id                      turn                             created_at                status
per_005cc6b90001wptm4ESDlQ0wlm  opencode-turn-b7ddb8d6-...       2026-08-15T14:21:18.104Z  pending
per_005d54cc0001f1q32zDJCj2PCI  opencode-turn-44264957-...       2026-08-15T14:31:00.040Z  pending
per_005d54d0d001bmo190FFDXuhCI  opencode-turn-44264957-...       2026-08-15T14:31:00.117Z  pending
per_005d54df8001xr5TstpGd1gKVD  opencode-turn-44264957-...       2026-08-15T14:31:00.350Z  pending

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:

case "permission.asked": {
  context.pendingPermissions.set(event.properties.id, event.properties);   // ~:942
  yield* emit({ ...base, type: "request.opened", payload: { ... } });
}

case "permission.replied": {
  context.pendingPermissions.delete(event.properties.requestID);           // ~:964
  yield* emit({ ...base, type: "request.resolved", payload: { ... } });
}

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:

const stopOpenCodeContext = Effect.fn("stopOpenCodeContext")(function* (context) {
  if (yield* Ref.getAndSet(context.stopped, true)) return false;
  yield* runOpenCodeSdk("session.abort", () =>
    context.client.session.abort({ sessionID: context.openCodeSessionId }),
  ).pipe(Effect.ignore({ log: true }));
  yield* Scope.close(context.sessionScope, Exit.void);
  return true;
});

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:

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

Activity

  1. maslinedwin commented on Aug 16, 2026

    @maslinedwin
    Contributor

    Fix is in #7173.

  2. WesSouza commented on Aug 19, 2026

    @WesSouza

    The following was generated by gpt-5.6-luna medium running after npx t3@nightly triage:


    Additional reproduction: T3 Code desktop app + OpenCode 1.18.18 + LM Studio local model (Qwen 3.8 27B), macOS arm64, T3 Code nightly 0.0.34-nightly.20260819.1132.

    Steps:

    1. Use the OpenCode provider with a local LM Studio model.
    2. Run in Auto/Auto-accept Edits mode.
    3. Ask the agent to inspect a codebase; for example, search for background-refresh symbols.
    4. OpenCode emits permission.asked for grep (and similar unlisted tools).
    5. The T3 work log shows Approval requested, but no actionable approval controls appear; the thread remains Working.
    6. After waiting or stopping the run, the provider emits MessageAbortedError: Aborted.

    Relevant sanitized provider-event evidence from the affected desktop session:

    • permission.asked: permission="grep", with patterns such as "lastRefresh|refreshDue|isDue|stale" and "BGAppRefresh|beginBackgroundTask".
    • T3 canonical event: request.opened with requestType="unknown".
    • Later: session.error with error name MessageAbortedError and data message "Aborted".
    • The corresponding tool parts end with "Tool execution aborted".

    This reproduces the same failure mode described here: OpenCode permission kinds outside bash/read/edit are mapped to unknown, leaving an approval request that the desktop UI cannot render. The stopped session then leaves the pending request lifecycle exposed.

    Full Access avoids the issue because OpenCode receives a catch-all allow rule. Auto-accept Edits does not cover grep/task/skill-style permissions. Related to #4795 and #7484.

  3. Mchicao commented on Aug 20, 2026

    @Mchicao

    This need to be fixed :(

  4. devanshtakkar commented on Aug 21, 2026

    @devanshtakkar

    This need to be fixed :(

    Absolutely! Can’t believe it’s been a week already and this bug makes opencode unusable with t3-code.

    Only if theo and his team find the time to make the most used open source agent work decently rather than trying to integrate grok/ cursor or whatever into t3-code. 😐

  5. Xzese commented on Aug 22, 2026

    @Xzese

    Yep this still isn't fixed for me. I raised #7843 and #7657 which were just closed despite referencing versions higher than the one this release came out on...

  6. Xzese commented on Aug 23, 2026

    @Xzese

    Additional reproduction/evidence from 2026-08-23:

    Environment:

    • T3 Code 0.0.34-nightly.20260823.1169
    • macOS 25.5.0 arm64
    • T3 Code Nightly desktop app with its local server
    • OpenCode 1.18.21
    • OpenCode runtime mode: auto

    A thread was investigating whether an existing container workspace still referenced the correct /workspace location.
    A normal bash approval appeared and was accepted. The next OpenCode permission request was for grep while
    searching the container configuration for workspace references.

    The provider log shows this as permission: "grep" and T3 emits requestType: "unknown". No approval UI appeared;
    the user only saw the chat remain in “Working” with its Work Log visible.

    Read-only local state showed:

    • latest turn still running
    • pending_approval_count = 1
    • one projection_pending_approvals row with status = 'pending'
    • no corresponding resolution for that request

    The installed source confirms the path: OpenCodeAdapter maps only bash, read, and edit; other permissions
    become unknown. The web client’s derivePendingApprovals ignores unknown request types before selecting the active
    composer approval. This is the live, pre-teardown form of the stale approval leak described here; interrupting or
    restarting the session would likely leave the pending row stranded.

    No database or source changes were made.

    Model/agent: GPT-5 Codex, T3 triage.

  7. added a commit that references this issue on Sep 4, 2026
    01f3e50
  8. added a commit that references this issue on Sep 5, 2026
    3ada204
  9. added a commit that references this issue on Nov 2, 2026
    8183a58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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