Summary
permission.edit (and by extension write) rules are matched against the worktree-relative path of the file being edited, not the absolute path. Any rule using an absolute path or ~ pattern silently never matches. This is fail-open for deny rules: a rule like "~/.ssh/**": "deny" looks like it protects a directory but does nothing.
Environment
- opencode 1.18.13 (Homebrew, macOS)
- Config:
~/.config/opencode/opencode.jsonc
Root cause
The edit/write tools submit a worktree-relative path as the permission pattern:
packages/opencode/src/tool/write.ts:56 — patterns: [path.relative(instance.worktree, filepath)]
packages/opencode/src/tool/edit.ts:104 and :147 — same
For a file outside the worktree this yields paths with ../.. segments (e.g. ../../daily-notes/2026-08.md), which absolute/~ patterns can never match.
Repro
- Global config:
- Run:
opencode run --agent writer "Create the file ~/daily-notes/test.txt containing ok"
Expected: edit allowed by the ~/daily-notes/**" allow rule.
Actual: denied — the submitted pattern is relative (e.g. ../../daily-notes/test.txt from a repo cwd, Users/<name>/daily-notes/test.txt from /tmp), so the allow rule never matches and "*": "deny" wins.
The fail-open direction (security)
A user writing this believes ~/.ssh is protected. The deny rule never matches, so the agent can edit ~/.ssh/* freely. Any absolute-path deny rule for edit/write is currently a no-op.
Workaround (what I had to do)
Use relative patterns that anticipate ../ segments, which is undocumented and fragile:
This also over-broadens the rule: any directory named daily-notes anywhere becomes writable.
Suggestion
Resolve the submitted pattern to an absolute path before matching (or match against both absolute and relative forms), so absolute and ~ patterns work as users expect. At minimum, document that edit/write permission patterns are worktree-relative and that absolute/~ patterns do not work.
Happy to provide more detail or test a fix.
Summary
permission.edit(and by extensionwrite) rules are matched against the worktree-relative path of the file being edited, not the absolute path. Any rule using an absolute path or~pattern silently never matches. This is fail-open fordenyrules: a rule like"~/.ssh/**": "deny"looks like it protects a directory but does nothing.Environment
~/.config/opencode/opencode.jsoncRoot cause
The edit/write tools submit a worktree-relative path as the permission pattern:
packages/opencode/src/tool/write.ts:56—patterns: [path.relative(instance.worktree, filepath)]packages/opencode/src/tool/edit.ts:104and:147— sameFor a file outside the worktree this yields paths with
../..segments (e.g.../../daily-notes/2026-08.md), which absolute/~patterns can never match.Repro
{ "agent": { "writer": { "mode": "all", "permission": { "bash": "deny", "edit": { "*": "deny", "~/daily-notes/**": "allow" } } } } }opencode run --agent writer "Create the file ~/daily-notes/test.txt containing ok"Expected: edit allowed by the
~/daily-notes/**" allowrule.Actual: denied — the submitted pattern is relative (e.g.
../../daily-notes/test.txtfrom a repo cwd,Users/<name>/daily-notes/test.txtfrom/tmp), so theallowrule never matches and"*": "deny"wins.The fail-open direction (security)
A user writing this believes
~/.sshis protected. Thedenyrule never matches, so the agent can edit~/.ssh/*freely. Any absolute-pathdenyrule foredit/writeis currently a no-op.Workaround (what I had to do)
Use relative patterns that anticipate
../segments, which is undocumented and fragile:This also over-broadens the rule: any directory named
daily-notesanywhere becomes writable.Suggestion
Resolve the submitted pattern to an absolute path before matching (or match against both absolute and relative forms), so absolute and
~patterns work as users expect. At minimum, document thatedit/writepermission patterns are worktree-relative and that absolute/~patterns do not work.Happy to provide more detail or test a fix.