You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
permission.edit patterns are matched against worktree-relative paths — absolute/~ patterns silently never match (fail-open for deny rules) #40945
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:
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)
"edit": { "*": "allow", "~/.ssh/**": "deny" }
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 nameddaily-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.
This issue might be a duplicate of existing issues. Please check:
Permission glob patterns in agent .md files silently never match — docs show relative paths but runtime uses absolute paths #38351: Same root cause — permission glob patterns silently never match due to relative vs. absolute path mismatch in the permission matcher. That issue describes the inverse direction (runtime uses absolute paths but patterns are relative), while this issue describes absolute/~ patterns failing against worktree-relative submitted paths. Both stem from the same path normalization gap in the permission matching pipeline.
I’d be wary of the “match both” fix. Since evaluate() is last-match-wins, running one target through two path namespaces leaves an awkward question: relative hits allow, absolute hits deny — which one gets to be “last”?
In BitFun we handle the target side at the tool boundary: turn it into one canonical absolute permission resource, then evaluate once. New files need the non-obvious bit: canonicalize the nearest existing ancestor and append the missing tail, otherwise ~/.ssh/new_key falls straight through because it does not exist yet. I’d normalize configured globs into that same namespace and add the creation case to the regression alongside the two existing repros.
Activity
github-actions commentedon Aug 6, 2026
This issue might be a duplicate of existing issues. Please check:
~patterns failing against worktree-relative submitted paths. Both stem from the same path normalization gap in the permission matching pipeline.bobleer commentedon Aug 6, 2026
I’d be wary of the “match both” fix. Since
evaluate()is last-match-wins, running one target through two path namespaces leaves an awkward question: relative hitsallow, absolute hitsdeny— which one gets to be “last”?In BitFun we handle the target side at the tool boundary: turn it into one canonical absolute permission resource, then evaluate once. New files need the non-obvious bit: canonicalize the nearest existing ancestor and append the missing tail, otherwise
~/.ssh/new_keyfalls straight through because it does not exist yet. I’d normalize configured globs into that same namespace and add the creation case to the regression alongside the two existing repros.github-actions commentedon Oct 6, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.