Bug Description
User-level permission config at ~/.config/opencode/opencode.jsonc is not effective when a project has .opencode/opencode.jsonc with minimal/missing permission rules.
Steps to Reproduce
- Create user-level config at
~/.config/opencode/opencode.jsonc:
- Have a project with
.opencode/opencode.jsonc containing:
- Start OpenCode in the project directory
- Run a
read operation or supabase db query command
Expected Behavior
User-level read/bash permissions should be respected since the project config doesn't specify them.
Actual Behavior
Both read and bash (e.g., supabase *) still prompt for permission approval.
Root Cause Analysis
Config loading hierarchy (from src/config/paths.ts):
- System-managed (highest priority)
- Project-level
.opencode/opencode.jsonc
- User-level
~/.config/opencode/opencode.jsonc (lowest priority)
When configs are merged, the project-level config takes priority. If the project config has a permission key (even with minimal rules like just edit), it may replace rather than deep-merge the user-level permission object, causing user-level read/bash rules to be lost.
Evidence from src/agent/agent.ts:102-109:
const user = Permission.fromConfig(cfg.permission ?? {})
Here cfg.permission is the already-merged config. If project-level permission overwrites user-level permission during config merge (shallow merge on the permission key), the user's read/bash rules are gone.
Additionally, evaluate.ts:10 uses findLast() semantics:
const match = rules.findLast(
(rule) => Wildcard.match(permission, rule.permission) && Wildcard.match(pattern, rule.pattern),
)
This returns the last matching rule, not the most specific one, making rule ordering critical and unintuitive.
Proposed Fix
- Use deep merge for the
permission object across config layers (user → project → system)
- OR: Change config priority so user-level overrides project-level for permission rules (user knows best)
- Consider using specificity-based matching instead of
findLast()
Environment
- OpenCode: built from source (dev branch)
- macOS 15.4 (Darwin 25.4.0)
- Config locations verified:
~/.config/opencode/opencode.jsonc + .opencode/opencode.jsonc
🤖 Generated with Claude Code
Bug Description
User-level permission config at
~/.config/opencode/opencode.jsoncis not effective when a project has.opencode/opencode.jsoncwith minimal/missing permission rules.Steps to Reproduce
~/.config/opencode/opencode.jsonc:{ "permission": { "read": { "*": "allow" }, "bash": { "*": "ask", "supabase *": "allow", "git *": "allow" } } }.opencode/opencode.jsonccontaining:{ "permission": { "edit": { "packages/opencode/migration/*": "deny" } } }readoperation orsupabase db querycommandExpected Behavior
User-level read/bash permissions should be respected since the project config doesn't specify them.
Actual Behavior
Both
readandbash(e.g.,supabase *) still prompt for permission approval.Root Cause Analysis
Config loading hierarchy (from
src/config/paths.ts):.opencode/opencode.jsonc~/.config/opencode/opencode.jsonc(lowest priority)When configs are merged, the project-level config takes priority. If the project config has a
permissionkey (even with minimal rules like justedit), it may replace rather than deep-merge the user-level permission object, causing user-level read/bash rules to be lost.Evidence from
src/agent/agent.ts:102-109:Here
cfg.permissionis the already-merged config. If project-levelpermissionoverwrites user-levelpermissionduring config merge (shallow merge on thepermissionkey), the user's read/bash rules are gone.Additionally,
evaluate.ts:10usesfindLast()semantics:This returns the last matching rule, not the most specific one, making rule ordering critical and unintuitive.
Proposed Fix
permissionobject across config layers (user → project → system)findLast()Environment
~/.config/opencode/opencode.jsonc+.opencode/opencode.jsonc🤖 Generated with Claude Code