Repository navigation
Description
Summary
In Plan mode, write and edit are refused for files inside the configured plan directory. The error names /Users/<user>/.opencode/plan as the Plan directory, then rejects /Users/<user>/.opencode/plan/003-example.md as being outside it.
This is the same failure as #44694, which was closed as not reproducible on beta-18050. It still reproduces on beta-18721, on a different OS and architecture (macOS arm64 vs the original Linux x86_64).
This report adds a discriminating test the original lacked: in the same Plan-mode session, a write to a project-relative aidocs/ path succeeds while a write to the absolute plan directory fails. Both paths have matching allow rules in the agent's resolved permissions. That isolates the fault to the guard's plan-path resolution and rules out both the permission layer and tool-selection/prompting.
Environment
- opencode version: 0.0.0-beta-18721
- OS: Darwin 25.6.0 (darwin arm64), macOS 26.6.2
- Terminal: TERM_PROGRAM=ghostty, TERM=xterm-256color, COLORTERM=truecolor
- Shell: /bin/zsh
- Install/channel: beta
- Active plugins:
advisor.ts,memory.ts,sartiq-models.ts,tavily-fallback.ts(all under~/.config/opencode/plugins/)
Reproduction
-
Configure the
planagent with a plan-directory carve-out in~/.config/opencode/opencode.json:"plan": { "permissions": [ { "action": "edit", "resource": "*", "effect": "deny" }, { "action": "edit", "resource": "~/.opencode/plan/*", "effect": "allow" }, { "action": "edit", "resource": "aidocs/*", "effect": "allow" }, { "action": "edit", "resource": "**/aidocs/*", "effect": "allow" } ] }
-
Confirm the rule resolves.
opencode2 api get /api/agentreports, for the Plan agent:{"action": "edit", "resource": "*", "effect": "deny"} {"action": "edit", "resource": "/Users/<user>/.opencode/plan/*", "effect": "allow"} {"action": "edit", "resource": "aidocs/*", "effect": "allow"} {"action": "edit", "resource": "**/aidocs/*", "effect": "allow"}The
~is expanded to an absolute path and the plan-directory rule is the last matchingeditrule, so it wins. -
Enter Plan mode. The injected reminder states:
You may optionally create or update plan documents in: /Users/<user>/.opencode/plan -
writea new file to~/.opencode/plan/003-example.md— denied. -
editan existing file, e.g.~/.opencode/plan/001-example.md— denied. -
In the same session,
writeto~/aidocs/guard-probe.md— succeeds.
Expected Behavior
Steps 4 and 5 succeed. The path matches an allow rule that resolves last, and the Plan-mode contract explicitly grants plan-directory writes.
Actual Behavior
Cannot use write to modify files outside the Plan directory: /Users/<user>/.opencode/plan
Cannot use edit to modify files outside the Plan directory: /Users/<user>/.opencode/plan
The error names the same directory the target file is in. Step 6, with a project-relative path, succeeds in the same session.
Additional Context
The aidocs/ contrast is the key new datum. Same Plan-mode session, same agent, two paths that both have allow rules:
| Target | Result |
|---|---|
~/.opencode/plan/003-example.md |
denied |
~/aidocs/guard-probe.md |
succeeded |
Because one succeeds, this is not a blanket Plan-mode write block, not the permission layer, and not a prompting/tool-selection problem (the same tool, in the same session, writes successfully to a different path). The guard discriminates by path, and does not recognise ~/.opencode/plan as the plan directory it names in its own error.
That the succeeding path is project-relative while the failing one is absolute is consistent with the original reporter's hypothesis in #44694: an unnormalized or wrongly-based comparison, e.g. the plan directory being resolved relative to the session/project location rather than to the advertised absolute path.
Other findings:
- Not a symlink or canonicalization issue on the user side:
~/.opencodeis a real directory, andos.path.realpath('/Users/<user>/.opencode/plan')returns the path unchanged. - Not a permission-layer failure: the same absolute path accepts a
writefrom thebuildagent in the same workspace. - The guard sits above the permission system, so no configuration change works around it.
Config.AgentEncodedexposes onlycolor, description, disabled, hidden, mode, model, permissions, request, steps, system, and the root config has no plan-directory field — so the plan directory is not user-configurable. - Reproducible consistently, across service restarts.
- Practical impact: the
planagent cannot write plan documents to the global plan directory at all. Workarounds are to write them frombuild, or to use a project-relativeaidocs/path.
Re: #44694
That issue was closed NOT_PLANNED with a request for a session transcript. I do not have a shareable transcript, but the aidocs/ contrast above should serve the same diagnostic purpose: it demonstrates the tool call is issued and succeeds against one path while being refused for another in the same session, which distinguishes a guard bug from a tool-selection problem.
Worth noting the maintainer tested beta-18050 and could not reproduce, while it reproduces reliably here on beta-18721. Something environmental may gate it. Details that may be relevant: the plan directory contains pre-existing files; ~/.opencode also contains memory/, node_modules/, package.json, and a .gitignore; and the session's working directory is the user's home directory (/Users/<user>), which is also the detected project root. If the guard bases the plan directory on the project location, a home-directory session may be the trigger.
Activity
holny commentedon Sep 2, 2026
Question before anyone picks this up: on current
v2the Plan plugin hardcodes~/.opencode/planas the plan directory (packages/core/src/plugin/plan.ts:31) — there's no configurable plan path in that plugin. How is the plan directory set in your setup (custom agent config?), and what does the effectiveallowrule look like when the absolute-path write is denied? Just want to reproduce the right configuration first.Xarianne commentedon Sep 29, 2026
Reproduces here with no custom config of any kind, it's the plugin's hardcoded ~/.opencode/plan as suspected.
Env: opencode v2.0.18, Linux x86-64 (Fedora bootc;). Session cwd = $HOME, which is also the project root; not a git repo. No opencode.json(c) anywhere, no agent/ dir, no plan-related config.
Resolved permissions for the plan agent (from GET /api/agent):
{ action: edit, resource: , effect: deny },
{ action: edit, resource: ~/.opencode/plan/, effect: allow },
{ action: external_directory, resource: ~/.opencode/plan/*, effect: allow }
Yet write to ~/.opencode/plan/.md fails with
Cannot use write to modify files outside the Plan directory:~/.opencode/plan, while writing the identical content to the identical path via the shell tool succeeds. Same as #49168.Since it reproduces with default config, the guard's plan-path resolution looks like the fault.
opencodejr commentedon Oct 28, 2026
Thanks for the detailed report and the
aidocs/comparison. I reproduced this on 2.2.0 (the reported beta isn't published on npm, so I used the latest release), and it still happens on the latestdev(06d3c06).Steps:
~.~/.opencode/plan/003-example.mdusing the absolute path.The tool fails with
Cannot use patch to modify files outside the Plan directory: <home>/.opencode/planand no file is created. My test usedpatch, which OpenCode offers instead ofwrite/editfor OpenAI-style models; those use the same edit permission check. When the home directory is outside the project, the same call succeeds.The recording and screenshot below show this, and the script below reproduces it. The TUI shows the target as the relative path
.opencode/plan/003-example.md. That fits your hypothesis: files inside the project are checked as relative paths, so the Plan agent's absolute allow rule for~/.opencode/plan/*never matches, and its deny-all edit rule wins. The 'outside the Plan directory' text is a rewrite of that permission denial.Until it's fixed, starting OpenCode from a directory that doesn't contain
~/.opencode/plan(e.g. a project subdirectory) avoids it.recording.mp4
Reproduction script (drive.ts)
Run it with opencode-drive:
opencode-drive run ./drive.ts. It drivesopencodefrom yourPATH; setOC_DEVto the path of an OpenCode checkout to run that from source instead.Lomare commentedon Oct 29, 2026
Two additions from #59838 (closed as a duplicate of this):
The error message is misleading in exactly the case where it fires. When the session cwd is an ancestor of the plan dir (e.g.
~), the write to~/.opencode/plan/is hard-denied with "Cannot use write to modify files outside the Plan directory" — even though the file is inside the plan directory. Root cause: the plan plugin's allow rule is absolute (<home>/.opencode/plan/*) but the permission check receives a cwd-relative resource when the target is inside the session cwd. I don't think #53365 (matching absolute rules against the full path) changes the message inplugin/plan.ts— so until the rule normalization lands, the message will keep misdirecting people.Workaround until it's fixed (add to the
planagent's permissions):{ "action": "edit", "resource": ".opencode/plan/*", "effect": "allow" }