Skip to content

Suggestion: session-pin workspace for stable macOS tool path resolution#27

Merged
Nomadcxx merged 3 commits intoNomadcxx:mainfrom
hawkff:upstream/session-pinned-workspace
Feb 13, 2026
Merged

Suggestion: session-pin workspace for stable macOS tool path resolution#27
Nomadcxx merged 3 commits intoNomadcxx:mainfrom
hawkff:upstream/session-pinned-workspace

Conversation

@hawkff
Copy link
Contributor

@hawkff hawkff commented Feb 12, 2026

Fixes #11

What

  • add per-session workspace pinning for plugin tool-hook path resolution
  • persist the first valid non-config workspace path per sessionID and reuse it if later calls lose worktree
  • harden config-dir detection by using path.relative(...) ancestry checks instead of string-prefix matching
  • avoid resolving relative file/shell tool paths into ~/.config/opencode when context is ambiguous
  • bound in-memory session workspace cache growth with a small cap

Why

This attempt to make workspace selection stable for the lifetime of a session, preventing path drift away from the active repo.

Tests

  • added regression test:
    • tests/unit/plugin-tools-hook.test.ts
    • verifies workspace is pinned from worktree and reused when later context omits worktree and only provides config-dir directory
  • also keeps existing worktree/config-dir resolution coverage

Validation

  • bun test tests/unit/plugin-tools-hook.test.ts
  • tmp_home=$(mktemp -d) && HOME="$tmp_home" bun run test:ci:unit && rm -rf "$tmp_home"
  • bun run test:ci:integration
  • bun run build

@remy90
Copy link
Contributor

remy90 commented Feb 12, 2026

Frustratingly, even #11 (comment) no longer works. Write access outside of the opencode config directory did work once but it hasn't worked since.
Hopefully I'll get some time at the weekend to investigate this further if it's not fully resolved before then

EDIT:
To clarify the above, I checked out your PR branch, built it and tested a cursor-acp model
in a directory outside of ~/.config/
The same results were seen between main and this branch as far as write access is concerned

@Nomadcxx Nomadcxx force-pushed the main branch 2 times, most recently from 9a5a975 to a344380 Compare February 12, 2026 18:35
@Nomadcxx Nomadcxx self-assigned this Feb 12, 2026
@Nomadcxx
Copy link
Owner

This is waaaay better than my initial fix/patch @hawkff

The problem IMO and we can't be certain unless we add some logging here but..

path.relative() is case-sensitive BUT macOS filesystems aren't. If homedir() returns /Users/Username/.config/opencode and opencode passes /Users/username/.config/opencode (different case), these are the same directory on macOS - but isWithinPath() treats them as different, so the config dir gets accepted as a valid workspace.

The Maybe Fix

Use realpathSync() to normalize paths before comparing:

import { realpathSync } from "fs";

function getOpenCodeConfigPrefix(): string {
  const configHome = process.env.XDG_CONFIG_HOME
    ? resolve(process.env.XDG_CONFIG_HOME)
    : join(homedir(), ".config");
  const configPath = join(configHome, "opencode");
  try {
    return realpathSync(configPath);
  } catch {
    return configPath;
  }
}

function isWithinPath(root: string, candidate: string): boolean {
  try {
    const realRoot = realpathSync(root);
    const realCandidate = realpathSync(candidate);
    const rel = relative(realRoot, realCandidate);
    return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
  } catch {
    const rel = relative(root, candidate);
    return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
  }
}

realpathSync() resolves symlinks AND normalizes case on macOS, so paths compare correctly.

@hawkff
Copy link
Contributor Author

hawkff commented Feb 12, 2026

What it adds:

  • realpath-aware workspace/config path comparison in src/plugin.ts
  • macOS-safe handling for case-variant path aliases during config-dir detection
  • regression test for symlink-alias config path (tests/unit/plugin-tools-hook.test.ts)

hawkff and others added 3 commits February 12, 2026 15:09
- add per-session workspace pinning for tool-hook path resolution\n- keep file/shell tool defaults anchored to non-config workspace paths\n- harden config-dir ancestry checks with path-relative logic\n- add regression coverage for worktree-loss within same session\n- bound session workspace cache growth
- normalize workspace/config path comparisons using realpath-aware canonicalization\n- harden macOS case-variant path handling during config-dir detection\n- add regression coverage for symlink-alias config path resolution
@hawkff hawkff force-pushed the upstream/session-pinned-workspace branch from f1b6924 to 091d1f2 Compare February 12, 2026 20:17
@hawkff
Copy link
Contributor Author

hawkff commented Feb 12, 2026

Hope this helps 🙈

@Nomadcxx Nomadcxx merged commit 3f131c5 into Nomadcxx:main Feb 13, 2026
2 checks passed
@hawkff hawkff deleted the upstream/session-pinned-workspace branch February 13, 2026 11:16
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.

Cursor agents don't have write access?

3 participants