Repository navigation
permissions: V2 “Always allow” silently overrides configured “ask” across future project sessions #48456
Description
Description
In OpenCode V2, selecting Always allow creates a persistent, project-scoped permission in SQLite. That permission takes precedence over an explicit ask rule in configuration and applies to future sessions, including subagents using the same project.
This makes the effective permission policy impossible to determine from the global and project configuration files alone. A user can configure SSH to require approval, then discover that an approval saved days or weeks earlier allows new sessions to execute SSH without prompting.
Additional Context
V1 and V2 have materially different approval lifetimes
In V1 1.18.22, approvals were stored in an in-memory approved array. They were scoped to a running directory instance, rather than strictly to an individual conversation, but were not persisted to disk.
In the inspected V2 implementation:
- saved approvals are loaded by project_id;
- saved allow rules are appended after configured rules;
- consequently, a saved allow takes precedence over configured ask;
- Always allow writes the approval to SQLite.
Configured deny is checked separately and remains enforced. This report concerns the loss of the expected approval boundary provided by ask.
Plugins
No response
OpenCode version
0.0.0-beta-19425
Steps to reproduce
The following scenario is supported by the observed session history and the V2 implementation. A fresh end-to-end reproduction was not performed.
- Configure SSH to require approval globally:
{
"permissions": [
{
"action": "shell",
"resource": "ssh *",
"effect": "ask"
}
]
}- In a project session, approve an SSH permission request using Always allow.
- End that session.
- Start a new session in the same project, without adding an allow rule to either the global or project configuration.
- Have the agent perform an action that invokes SSH.
The saved project permission allows SSH without a new approval prompt. Because the permission is stored in SQLite, its lifetime also extends beyond process restarts.
Expected Behavior
An explicit ask rule should remain a reliable approval boundary across new sessions.
Temporary approval and persistent policy changes should be clearly separated:
- Allow once approves one request.
- Allow for this session approves matching requests within the current session.
- Persistent project exceptions are explicitly configured in the project’s opencode.json.
I deliberately use global configuration for defaults and project configuration for exceptions. I should not need to remember an old UI approval stored separately in a database to understand why my configured ask no longer prompts.
Actual Behavior
During an investigation of a server disk-usage alert, OpenCode made 15 SSH calls without requesting fresh approval: - 14 from a subagent;
- one directly from the primary agent.
The configuration contained shell / ssh * / ask.
A saved permission record existed for the same project:
action: shell resource: ssh * created: 2026-09-01
The SSH calls occurred in a new session on 2026-09-11.
The saved record and successful SSH execution were confirmed in local storage. The original interaction that created the saved approval was not recovered.
Screenshot and/or share link
No response
Operating System
macOS / Darwin 25.6.0, arm64
Terminal
ghostty/tmux (TERM=tmux-256color)
Activity
github-actions commentedon Sep 11, 2026
This issue might be a duplicate of existing issues. Please check:
findLastordering. That issue proposes scoping approvals by sessionID as a fix and is closely related to the SQLite-persisted approval behavior you describe.askbeing executed without prompting, apparently due to a previously saved approval bypassing the configured rule.lingfish commentedon Sep 20, 2026
I just came across this v1 to v2 change in behaviour too (v2.0.10).
I actually like that it persists, but would think a better place would be in JSON. I don't see any way currently to remove the allowed permissions.
Ideally, both choices would be good - persist to JSON, or just for current in-memory session.
don't like this UX, back to v1
aanbrn commentedon Oct 15, 2026
A concrete end-to-end reproduction for the mechanism described here (the report notes one was not performed) — OpenCode v2.0.29, macOS.
Config (a project's
opencode.json, V1 shape):{ "permission": { "bash": { "*": "allow", "git push *": "ask" } } }opencode debug confignormalizes it with theaskintact:The saved allow. A session on 2026-10-10 chose Always allow on a prompt, which persists a batch of shell allows for that project;
git push *is one of them:Symptom. With that row present, a
git pushruns without an approval prompt:(The second was a no-op — already in sync — so nothing was written; neither command raised a prompt.)
Effect.
evaluateInputbuildsconst all = [...rules, ...(yield* savedRules())]andevaluatetakesfindLast(), so the savedallowis the last match and the configuredasknever fires — for that project only; a project with no saved row still prompts as configured (saved rows are looked up byprojectID). It is silent and durable:debug configshows theaskintact (the config reads correct), and the guard was still off five days later in a new session.Confirmed against
packages/core/src/permission.tsat tagv2.0.29(evaluate→rulesets.flat().findLast(…);evaluateInput→const all = [...rules, ...(yield* savedRules())]).Record that a saved always-allow overrides a permission ask (#580)