Repository navigation
Bug report: openspec-plan agent cannot create/edit proposals in a git-less repo #14
Description
Plugin: opencode-plugin-openspec v0.1.4 (injected agent openspec-plan, mode primary)
OpenCode: 1.18.5
Summary
If the OpenCode session is started in a directory that is not yet a git repository, the openspec-plan agent is denied on every edit call. It cannot create openspec/changes/<name>/proposal.md, cannot write spec files, cannot update AGENTS.md — the whole OpenSpec workflow is blocked.
The cause is an interaction between (a) the plugin's edit permission allowlist, which uses bare relative patterns (AGENTS.md, openspec/**, specs/**, project.md), and (b) OpenCode's edit tool, which checks permissions against path.relative(instance.worktree, filePath). In a git-less directory OpenCode binds the session to the global project whose
worktree is /, so every edit pattern comes out as Users/jones/src/github/clipclop/openspec/... — which never matches the plugin's relative patterns. The catch-all "*": "deny" then blocks all edits.
Steps to reproduce
- Create a directory that is not a git repo (no
git init).mkdir /tmp/openspec-test && cd /tmp/openspec-test - Start OpenCode there with the plugin installed.
- Select the
openspec-planagent and ask it to propose a new change, e.g. createopenspec/changes/foo/proposal.md. - Every attempt to write/update a file is denied with a permission error.
Expected: the agent edits files under openspec/**, specs/**, and AGENTS.md without approval (per its own permission config).
Actual: all edit calls fail. Example from OpenCode's log:
message=evaluated permission=edit
pattern="Users/jones/src/github/clipclop/openspec/changes/build-clipclop/proposal.md"
action.permission=edit action.pattern=* action.action=deny
Root cause
OpenCode derives the project for a session via Project.fromDirectory
(packages/opencode/src/project/project.ts):
const worktree = data.id === ProjectV2.ID.make("global") && !data.vcs ? "/" : data.directoryA directory with no VCS resolves to the global project, whose worktree is the filesystem root /. This is stored per session in ~/.local/share/opencode/opencode.db:
session: id=ses_03f7c5804ffelscnVxgTWvB0DE project_id=global
project: id=global worktree=/
The edit tool (packages/opencode/src/tool/edit.ts) then asks permission with a path pattern relative to that worktree:
patterns: [path.relative(instance.worktree, filePath)]With worktree === "/", editing
/Users/jones/src/github/clipclop/AGENTS.md produces the pattern
Users/jones/src/github/clipclop/AGENTS.md. The plugin's allowlist patterns
are bare relative paths:
edit: {
"*": "deny",
"project.md": "allow",
"AGENTS.md": "allow",
"openspec/**": "allow",
"specs/**": "allow"
}Wildcard.match is plain glob (* → .*, / not special-cased), so Users/jones/.../AGENTS.md matches neither AGENTS.md nor openspec/**. Rules are last-match-wins (findLast), so the "*": "deny" catch-all is the only match — every edit is denied.
Note: once the session is bound to global, it stays that way for the session's lifetime. Running git init afterwards does not rebind it; only a fresh session in a now-git repo resolves correctly.
Why this is especially bad for this plugin
openspec-plan is a planning agent whose entire job is creating and updating OpenSpec artifacts. But because OpenSpec drives everything from openspec/changes/<name>/proposal.md (a file creation), a user is most likely to hit this on the first run in a brand-new project — precisely before they've had a chance to git init. There is no recovery path within the session: the agent cannot even bootstrap the repo with git init because its bash permission is also an allowlist (openspec, grep, ls, cat, find, echo, pwd, which, env, printenv, git status*, git log*, git diff*, git show*) that does not include git init.
Suggested fixes (any one)
- Use prefix-tolerant patterns in the agent permission. Since
Wildcard.matchtreats*as.*and does not anchor on/, patterns like**/AGENTS.md,**/openspec/**,**/specs/**,**/project.mdmatch regardless of how the path is prefixed (worktree/or repo root). - Detect and warn: if
ctx.project.id === "global"(git-less directory), skip injecting the restricted agent, or surface a message telling the user togit initand restart. - Bootstrap git: the plugin's
bashallowlist could includegit initso the agent can fix the precondition itself, then instruct a restart.
Activity
jones77 commentedon Aug 2, 2026
happy to submit a PR, lmk