anomalyco / anomalyco/opencode

plan: Plan-mode guard denies writes to the configured plan directory it names (still present on beta-18721, macOS arm64)

Open
#46,358 1 comment 0 reactions 1 assignee View on GitHub

@rekram1-node is already working on this.

Since Aug 31, 2026.

2.0
Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

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

  1. Configure the plan agent 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" }
      ]
    }
    
  2. Confirm the rule resolves. opencode2 api get /api/agent reports, 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 matching edit rule, so it wins.

  3. Enter Plan mode. The injected reminder states: You may optionally create or update plan documents in: /Users/<user>/.opencode/plan

  4. write a new file to ~/.opencode/plan/003-example.mddenied.

  5. edit an existing file, e.g. ~/.opencode/plan/001-example.mddenied.

  6. In the same session, write to ~/aidocs/guard-probe.mdsucceeds.

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: ~/.opencode is a real directory, and os.path.realpath('/Users/<user>/.opencode/plan') returns the path unchanged.
  • Not a permission-layer failure: the same absolute path accepts a write from the build agent in the same workspace.
  • The guard sits above the permission system, so no configuration change works around it. Config.AgentEncoded exposes only color, 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 plan agent cannot write plan documents to the global plan directory at all. Workarounds are to write them from build, or to use a project-relative aidocs/ 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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.