Skip to content

edit permission uses relative paths but external_directory uses absolute paths, breaking agent-level path rules #20045

Closed as not planned

Description

@fluency03

Description

When configuring agent-level permissions with absolute path patterns, external_directory works correctly but edit silently fails to match because they use different path formats internally.

Agent config (YAML frontmatter):

permission:
  edit:
    "*": ask
    "/tmp/my-tmp-folder/**": allow
  external_directory:
    "/tmp/my-tmp-folder/**": allow

Expected: Both permissions resolve to allow for files under /tmp/my-tmp-folder/.

Actual: external_directory resolves to allow, but edit resolves to ask (prompts the user).

Root cause

In write.ts (line 26) and edit.ts (line 45), the edit permission check passes a relative path:

await ctx.ask({
  permission: "edit",
  patterns: [path.relative(Instance.worktree, filepath)],
  // ...
})

But in external-directory.ts (line 18), the external_directory check passes an absolute path:

const glob = path.join(dir, "*")
await ctx.ask({
  permission: "external_directory",
  patterns: [glob],
  // ...
})

For non-git projects, Instance.worktree is / (as noted in the comment in instance.ts). So:

path.relative("/", "/tmp/my-tmp-folder/file.md")
// -> "tmp/my-tmp-folder/file.md"   (no leading slash)

The configured pattern /tmp/my-tmp-folder/** produces regex ^/tmp/my-tmp-folder/.*$, which does NOT match tmp/my-tmp-folder/... (missing leading /).

The "*": ask catch-all is the last matching rule, so the user gets prompted.

Steps to reproduce

  1. Create an agent markdown file with the config shown above
  2. Start OpenCode in a non-git directory (so Instance.worktree is /)
  3. Have the agent write a file to /tmp/my-tmp-folder/test.md
  4. Observe the permission prompt despite the allow rule

Suggested fix

Normalize the path format so edit and external_directory use the same representation when matching against permission rules. Either:

  • Use absolute paths consistently in both checks, or
  • Document clearly that edit patterns are relative to the worktree and external_directory patterns are absolute

The first option is better UX since users configure both in the same permission block and expect the same path format.

Environment

  • OpenCode version: 1.3.0
  • OS: macOS (arm64)

Issue is discovered by OpenCode :)

Activity

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Mar 30, 2026
  2. github-actions commented on Mar 30, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

  3. ariane-emory commented on Mar 30, 2026

    @ariane-emory
    Contributor

    Using absolute paths seems like the wrong choice here, in my opinion it would likely be better to regain consistency by allowing relative paths in external_directory.

  4. fluency03 commented on Mar 30, 2026

    @fluency03
    Author

    in my experience, relative paths are bit tricky to manage.

    one of my pattern of using OpenCode is running opencode serve in one place and have it running, and run opencode attach to start a client - this is so that I am not starting an OpenCode server everytime which takes more resources on my machine and has a relatively slow startup time.

    however, what I have observed is that the "working directory" is based on where opencode serve (/path/A) is started instead of where opencode attach is started (/path/B, /path/C). this has been bugging me for a while.

  5. ariane-emory commented on Mar 30, 2026

    @ariane-emory
    Contributor

    @fluency03 Typically, I have multiple copies of any given git repository located at different paths and run individual instances of OpenCode in each one simultaneously. If they had to share absolute paths in the permissions in the project-local settings then that would plainly run into problems.

  6. fluency03 commented on Mar 30, 2026

    @fluency03
    Author

    @ariane-emory I do see the rational of that.

    But in my case

    • I decided to run opencode serve in one place, have it running, and run opencode attach to start different clients at different places because:
      • I do see a much faster start up time of the UI comparing to starting a server everytime (plain opencode) especially some of the MCP servers can be heavy;
      • (please correct me if I am wrong) and I think single server means single set of MCPs running, whereas multiple server means multiple set of MCPs running -> this can be expensive in CPU and memory usage on my machine;
        • imagine you have 5 servers running, if each server has 3 MCPs enabled, that's already 15 MCP processes running...
      • I am not even running opencode attach for the same projects in different copies. I am running multiple clients for all of my work across Q&A, multiple different projects, etc, in order to gain the fast start up time and low CPU/memory.
        • and specifically, I have lots of use cases where I just need to do some investigation or document review - these tasks are not bound to any specific git repository;
    • Regarding share absolute paths in the permissions, i'd like to write some of the intermediate content of aubagents and thinking processes of the agent into files so that I can review them later. And I'd prefer to use /tmp. I think it should be ok to share permissions to /tmp or a specific directly of /tmp. And on top of that, I am only giving /tmp/some-dir in the custom agent config, not on the top level of OpenCode.

    and regardless, there is an inconsistency between how path is recognized in external_directory vs. edit which can be confusing and it's not documented.

    thanks!

  7. CarloWood commented on Apr 14, 2026

    @CarloWood
    Contributor

    The globbing can simply happen against the relative path OR the absolute path as a function of whether or not the glob pattern looks like a relative path or an absolute path itself.

    Currently all "edit" patterns look like relative paths, or they would never match anything.
    There is no reason not to match patterns (added in the future) that look like absolute paths (aka, the pattern starts with '/' on linux, or 'C://' or whatever on Windows) against the absolute filepath.

    On top of that I think we really should use proper globbing. Having a * match anything even ../ is unacceptable.

  8. removed
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 3, 2026
  9. boazsegev commented on May 8, 2026

    @boazsegev

    The only way to avoid leaking personal data to external providers (such as internal folder structure, user names, etc') is by always using relative paths. Any tool that returns an absolute path to the AI model is leaking private data.

  10. CarloWood commented on May 8, 2026

    @CarloWood
    Contributor

    @boazsegev I use environment variables for all my absolute paths, also in the opencode.json - that way you don't give anyway anything.

  11. boazsegev commented on May 8, 2026

    @boazsegev

    @CarloWood - the read and write tools seem to return the absolute path for the files, leaking the information back to the models. Your environment variables circumvent that? How?

  12. github-actions commented on Jul 9, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

  13. MataM15 commented on Jul 21, 2026

    @MataM15

    This is still very much alive on opencode 1.17.20 (2026-07-21), on a real project — not just the synthetic repro. Auto-closed by the 60-day stale bot with state_reason: not_planned, but nothing was actually fixed.

    Reproduction, from ~/.local/share/opencode/log/opencode.log:

    Config (~/.config/opencode/opencode.json):

    "permission": {
      "external_directory": {
        "*": "ask",
        "/tmp/opencode/**": "allow"
      }
    }

    Headless review subagents (mode: subagent, no interactive prompt available) tried to read a diff file written to /tmp/opencode/<lineage>.diff. Log:

    evaluated permission=external_directory pattern=/tmp/opencode/* action.pattern=* action.action=deny
    

    The /tmp/opencode/** rule never matches because (per the source referenced in this issue) external_directory always checks against path.join(dir, "*") — a fixed single-star suffix — not the actual file path. Since the specific allow rule can't match, it falls through to the "*": "ask" catch-all, and since the subagent is headless, unresolved ask degrades to deny. Same root cause as #14445 and the general glob-matching issue in #22465.

    Workaround that unblocked us: add an extra literal-single-star entry alongside the globstar one:

    "/tmp/opencode/**": "allow",
    "/tmp/opencode/*": "allow"

    Reopening would help — this is a genuine footgun for anyone using headless/subagent review pipelines with external_directory rules, not resolved by the closure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions