Skip to content

[Bug]: OpenCode Auto mode ignores user opencode permission patterns and asks for every bash, MCP, and webfetch call #11458

Description

@TrueBurn
  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area: apps/server

Steps to reproduce

  1. Run T3 Code with the OpenCode provider (opencode 1.18.30, runtime build sha256-02c38816, WSL2 runtime) and keep the thread runtime mode on Auto.
  2. Define fine-grained permissions in ~/.config/opencode/opencode.jsonc, for example: "permission": { "bash": { "*": "allow", "rm -rf *": "ask", "git push *": "ask", "kubectl delete *": "ask" } }.
  3. Send a turn that runs ls, then a turn that calls any MCP tool (I used context7 query-docs), then one that fetches a URL.
  4. All three stop on the approval dialog (Allow once / Allow for workspace / Deny).

Expected behavior

Auto mode for the OpenCode provider should honor the permission configuration opencode already supports. With the config above, ls and the MCP call run without approval and git push asks. Auto then becomes the fine-grained middle ground between Supervised and Full access, instead of a second Supervised.

Actual behavior

The OpenCode adapter injects its own rule set on every session.create/session.update and overrides the user's config. buildOpenCodePermissionRules("auto") returns *: * -> ask, then allow-lists read (with .env ask), glob, grep, lsp, skill, todowrite, question, and leaves bash, edit, webfetch, websearch, codesearch, external_directory, and doom_loop on ask. MCP tools match the catch-all, so every MCP call asks. The patterns the user wrote in opencode.jsonc never reach opencode.

Findings

  • The installed server bundle (apps/server/dist/bin.mjs, runtime dir sha256-02c38816d0b6722af4ec74c48982ab009586fe7f84274cc757fe8f039c4784db) carries buildOpenCodePermissionRules(runtimeMode) as a binary branch on full-access. Every non-full-access mode shares one rule list; only edit flips to allow under auto-accept-edits.
  • The adapter applies the list at session.create({ ..., permission: buildOpenCodePermissionRules(input.runtimeMode) }), and again at session.update({ sessionID, permission: ... }) on resume and fork, so the override survives session reuse.
  • The Claude driver maps the same thread mode onto Claude Code's own policy: auto -> --permission-mode auto, auto-accept-edits -> acceptEdits, full-access -> bypassPermissions. I verified both behaviors in the same session pair: the identical ls, MCP call, and webfetch ran without any approval under the Claude provider in Auto mode and each prompted under the OpenCode provider in Auto mode. One mode label, two policies.
  • Full access is the only escape, and it removes gating entirely: *: allow, external_directory: allow, plus auto-reply to any residual ask. That is too coarse for day-to-day work, so people either approve constantly or run ungated.

Validation

  • sqlite3 (read-only) on ~/.t3/userdata/state.sqlite reports runtime_mode = 'auto' for the affected threads, and the T3 server process env contains none of the opencode permission config, so the injected rules are the only permissions opencode sees.
  • I extracted and quoted the rule list above from the running bundle rather than from docs or memory.
  • I reproduced the prompts listed in the repro steps on this thread before writing this report.
  • The OpenCode provider session receives the rules via the SDK permission field, which is why the global ~/.config/opencode/opencode.jsonc permission block has no effect under T3 while working as documented when running opencode standalone.

Suggested fix

session.create/session.update already accept permission rule arrays, and opencode already evaluates per-tool glob patterns with last-match-wins. When the runtime mode is Auto, pass the user's opencode permission config through to the session instead of the catch-all ask list, and fall back to the current defaults when no user config exists. This keeps Supervised as-is, keeps full access for people who want zero gates, and gives Auto an actual middle ground.

References

Activity

  1. RiccardoCherchi commented on Sep 13, 2026

    @RiccardoCherchi

    +1 on this issue, its not that good to have only ask for anything or allow all, i prefer to use a custom permissions ruleset

  2. KostasKgr commented on Sep 15, 2026

    @KostasKgr

    In the meantime I patched my local installation in the manner the fix suggests, while im evaluating if i want to use t3+opencode for local small models.

    vibe coded patch script follows in case it's useful to anyone.

    ❯ cat patch-t3-opencode-auto.sh
    #!/usr/bin/env bash
    
    set -euo pipefail
    
    usage() {
      cat <<'EOF'
    Usage: patch-t3-opencode-auto.sh [PATH_TO_T3_BIN_MJS]
    
    Without an argument, the script resolves the active `t3` executable and patches
    its installed dist/bin.mjs file. A timestamped backup is created beside the
    target before it is changed.
    EOF
    }
    
    if (( $# > 1 )); then
      usage >&2
      exit 2
    fi
    
    if (( $# == 1 )); then
      target=$1
    else
      t3_command=$(command -v t3 || true)
      if [[ -z "$t3_command" ]]; then
        echo "Error: t3 is not on PATH. Pass the path to its dist/bin.mjs explicitly." >&2
        exit 1
      fi
      target=$(readlink -f -- "$t3_command")
    fi
    
    if [[ ! -f "$target" ]]; then
      echo "Error: target is not a regular file: $target" >&2
      exit 1
    fi
    
    if [[ ! -w "$target" ]]; then
      echo "Error: target is not writable: $target" >&2
      exit 1
    fi
    
    function_line='function buildOpenCodePermissionRules(runtimeMode) {'
    full_access_line=$'\tif (runtimeMode === "full-access") return [{'
    inherit_line=$'\tif (runtimeMode === "auto") return [];'
    
    if grep -Fqx "$inherit_line" "$target"; then
      echo "Already patched: $target"
      exit 0
    fi
    
    function_count=$(grep -Fxc "$function_line" "$target" || true)
    full_access_count=$(grep -Fxc "$full_access_line" "$target" || true)
    if [[ "$function_count" != 1 || "$full_access_count" != 1 ]]; then
      echo "Error: this T3 build does not contain the expected permission function." >&2
      echo "No backup or modification was made." >&2
      exit 1
    fi
    
    timestamp=$(date -u +%Y%m%dT%H%M%SZ)
    backup="${target}.backup-${timestamp}"
    suffix=0
    while [[ -e "$backup" ]]; do
      suffix=$((suffix + 1))
      backup="${target}.backup-${timestamp}-${suffix}"
    done
    
    cp -p -- "$target" "$backup"
    
    target_dir=$(dirname -- "$target")
    temp_file=$(mktemp --tmpdir="$target_dir" '.t3-bin.mjs.patch.XXXXXX')
    cleanup() {
      [[ -z "${temp_file:-}" ]] || rm -f -- "$temp_file"
    }
    trap cleanup EXIT
    
    awk -v function_line="$function_line" -v inherit_line="$inherit_line" '
      $0 == function_line {
        print
        print inherit_line
        inserted++
        next
      }
      { print }
      END {
        if (inserted != 1) exit 42
      }
    ' "$target" >"$temp_file"
    
    if [[ $(grep -Fxc "$inherit_line" "$temp_file" || true) != 1 ]]; then
      echo "Error: patched output failed verification; original remains unchanged." >&2
      echo "Backup retained at: $backup" >&2
      exit 1
    fi
    
    chmod --reference="$target" "$temp_file"
    mv -- "$temp_file" "$target"
    temp_file=''
    
    echo "Patched: $target"
    echo "Backup:  $backup"
    echo "Restart T3 Code, then select Auto for the OpenCode thread."
    
  3. KostasKgr commented on Sep 16, 2026

    @KostasKgr

    Actually the current setup interferes also with different agent modes. E.g. I made a readonly agent and the agent's permissions are not honored. It ended up surprising me and editing files.

  4. hariappointy commented on Sep 16, 2026

    @hariappointy

    Same root cause hits the opposite direction too: explicit deny entries in ~/.config/opencode (e.g. "webfetch": "deny") are silently bypassed under Full access — the injected {"*": "allow"} session ruleset wins the last-match merge, so tools the user denied run with no prompt. Agent-level permission overrides and legacy tools: false don't survive it either.

  5. seankoji commented on Oct 11, 2026

    @seankoji

    the combined effect with #12248 means that I cant start new sessions in opencode and chatgpt providers without one of them breaking

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