Repository navigation
[Bug]: OpenCode Auto mode ignores user opencode permission patterns and asks for every bash, MCP, and webfetch call #11458
Description
Activity
+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
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."Reacted by Dakota Gravitt and Sean CareyActually the current setup interferes also with different agent modes. E.g. I made a
readonlyagent and the agent's permissions are not honored. It ended up surprising me and editing files.Same root cause hits the opposite direction too: explicit
denyentries 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-levelpermissionoverrides and legacytools: falsedon't survive it either.the combined effect with #12248 means that I cant start new sessions in opencode and chatgpt providers without one of them breaking
Area: apps/server
Steps to reproduce
~/.config/opencode/opencode.jsonc, for example:"permission": { "bash": { "*": "allow", "rm -rf *": "ask", "git push *": "ask", "kubectl delete *": "ask" } }.ls, then a turn that calls any MCP tool (I used context7query-docs), then one that fetches a URL.Expected behavior
Auto mode for the OpenCode provider should honor the permission configuration opencode already supports. With the config above,
lsand the MCP call run without approval andgit pushasks. 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.updateand overrides the user's config.buildOpenCodePermissionRules("auto")returns*: *-> ask, then allow-listsread(with.envask),glob,grep,lsp,skill,todowrite,question, and leavesbash,edit,webfetch,websearch,codesearch,external_directory, anddoom_loopon ask. MCP tools match the catch-all, so every MCP call asks. The patterns the user wrote inopencode.jsoncnever reach opencode.Findings
apps/server/dist/bin.mjs, runtime dir sha256-02c38816d0b6722af4ec74c48982ab009586fe7f84274cc757fe8f039c4784db) carriesbuildOpenCodePermissionRules(runtimeMode)as a binary branch onfull-access. Every non-full-access mode shares one rule list; onlyeditflips to allow underauto-accept-edits.session.create({ ..., permission: buildOpenCodePermissionRules(input.runtimeMode) }), and again atsession.update({ sessionID, permission: ... })on resume and fork, so the override survives session reuse.--permission-mode auto, auto-accept-edits ->acceptEdits, full-access ->bypassPermissions. I verified both behaviors in the same session pair: the identicalls, 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.*: 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.sqlitereportsruntime_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.permissionfield, which is why the global~/.config/opencode/opencode.jsoncpermissionblock has no effect under T3 while working as documented when running opencode standalone.Suggested fix
session.create/session.updatealready 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
auto-accept-editsonly; Auto still collapses to ask-all in the current build.