Skip to content

fix(plugin): reject '/' as workspace, fall back to $HOME#62

Open
yannicklescure wants to merge 1 commit intoNomadcxx:mainfrom
yannicklescure:fix/resolve-workspace-reject-root
Open

fix(plugin): reject '/' as workspace, fall back to $HOME#62
yannicklescure wants to merge 1 commit intoNomadcxx:mainfrom
yannicklescure:fix/resolve-workspace-reject-root

Conversation

@yannicklescure
Copy link
Copy Markdown

Summary

  • resolveWorkspaceDirectory previously accepted any path it was handed, including a bare filesystem root (/). When opencode is launched without a real cwd (systemd unit, login shell at /, daemon spawned by root), worktree, directory, and process.cwd() can all resolve to /, which silently makes every tool operate against the whole machine and bypasses workspace scoping.
  • Every candidate — env vars (CURSOR_ACP_WORKSPACE, OPENCODE_CURSOR_PROJECT_DIR), worktree, directory, and cwd — is now rejected if it resolves to / (or a bare Windows drive root like C:\).
  • When no usable workspace is found we fall back to the current user's home directory before the OpenCode config prefix, so tools always land in a writable, user-scoped location instead of /.
  • resolveWorkspaceDirectory and a new isRootPath helper are exported so the behaviour is unit-testable.

Why

The bundled ~/.config/opencode/plugin/cursor-acp.js was hot-patched in the field to work around this. This PR lands the same fix in the TypeScript source (src/plugin.ts) so a fresh bun run build produces a plugin that is safe by default.

Changes

  • src/plugin.ts
    • Add isRootPath(pathValue) — detects / and bare Windows drive roots.
    • Add isAcceptableWorkspace(path, configPrefix) — rejects empty, root, and config-prefix paths in one place.
    • Reorder resolveWorkspaceDirectory so the configPrefix is computed once, each candidate goes through isAcceptableWorkspace, and a $HOME fallback is consulted before the config prefix.
    • Export resolveWorkspaceDirectory and isRootPath.
  • tests/unit/plugin-workspace-resolution.test.ts (new)
    • isRootPath covers POSIX root, Windows drive roots (skipped off-Windows), and ordinary paths.
    • resolveWorkspaceDirectory covers: worktree preferred, directory fallback, / rejected from worktree/directory (falls back to cwd), / everywhere (falls back to \$HOME), env-var / rejection for both env vars, honoring a real env workspace, and skipping paths inside the config prefix.

Test plan

  • bun test tests/unit/plugin-workspace-resolution.test.ts — 10 pass, 1 skipped (Windows-only case).
  • bun test tests/unit/plugin.test.ts tests/unit/plugin-config.test.ts — no regressions.
  • bun run build — bundles cleanly.
  • Reviewer: verify on a real systemd-spawned daemon that the plugin no longer treats / as the workspace.

Note: tests/unit/plugin-tools-hook.test.ts > treats config path aliases (symlink/case variants) as config and falls back to workspace fails on main as well (unrelated symlink/tmp handling); not addressed here.

Made with Cursor

resolveWorkspaceDirectory previously accepted any path the daemon
received, including a bare filesystem root. When opencode is launched
without a sensible cwd (common for systemd units or shells started
at '/'), worktree/directory/cwd can all resolve to '/', which made
every tool operate against the whole machine and silently bypassed
workspace scoping.

Now every candidate — env vars, worktree, directory, cwd — is rejected
if it resolves to '/' (or a bare Windows drive root). When no usable
workspace is found we fall back to the current user's home directory
before the config prefix, so tools always land in a writable,
user-scoped location.

Also export resolveWorkspaceDirectory / isRootPath so the behaviour
can be unit-tested, and add tests/unit/plugin-workspace-resolution.test.ts
covering each rejection and fallback path.

Made-with: Cursor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant