Skip to content

fix: resolve symlinks in workspace path normalization#1235

Open
keepmind9 wants to merge 2 commits into
getpaseo:mainfrom
keepmind9:fix/workspace-symlink-resolution
Open

fix: resolve symlinks in workspace path normalization#1235
keepmind9 wants to merge 2 commits into
getpaseo:mainfrom
keepmind9:fix/workspace-symlink-resolution

Conversation

@keepmind9
Copy link
Copy Markdown

Summary

  • normalizeWorkspaceId used path.resolve() which does not follow symlinks
  • When --cwd uses a symlink path (e.g. /data/app/data1/app), workspace lookup fails because stored paths are real paths but input
    paths are symlink paths
  • Add fs.realpathSync to normalizeWorkspaceId, and normalize both input and stored paths in all comparison sites

Changes

  • workspace-registry-model.ts: Add realpathSync with try/catch fallback to normalizeWorkspaceId
  • workspace-directory.ts: Normalize stored workspace.cwd in both exact match and prefix match paths of
    resolveRegisteredWorkspaceIdForCwd
  • session.ts: Normalize stored workspace.cwd in findExactWorkspaceByDirectory
  • workspace-registry-model.test.ts: Add symlink resolution tests

normalizeWorkspaceId used path.resolve() which does not follow symlinks,
causing workspace lookup failures when --cwd uses a symlink path that
differs from the real path stored in workspace records. Add realpathSync
to normalize both input and stored paths during comparison.
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 30, 2026

Greptile Summary

This PR fixes workspace lookup failures that occurred when --cwd was passed as a symlink path: normalizeWorkspaceId now calls realpathSync after path.resolve, with a graceful fallback for non-existent paths, and all comparison sites in workspace-directory.ts and session.ts are updated to normalize workspace.cwd symmetrically.

  • workspace-registry-model.ts: normalizeWorkspaceId wraps path.resolve with realpathSync (+ try/catch), so both symlink and real paths collapse to the same canonical string.
  • workspace-directory.ts: Exact-match, prefix-match, and the home-directory guard all now call normalizeWorkspaceId on the stored workspace.cwd, removing the asymmetry between input and persisted paths.
  • session.ts: findExactWorkspaceByDirectory similarly normalizes workspace.cwd before comparing.

Confidence Score: 5/5

Safe to merge — the change is narrowly scoped to path normalization and all comparison sites are updated symmetrically.

The fix is logically sound: realpathSync is applied consistently on both the input path and every stored workspace.cwd before comparison, so symlink/real-path mismatches are eliminated in all three lookup sites. The try/catch fallback preserves existing behaviour for non-existent paths. No existing test semantics are broken, and the four new tests cover the key cases.

No files require special attention. The test file has a minor import consistency nit but the logic is correct.

Important Files Changed

Filename Overview
packages/server/src/server/workspace-registry-model.ts Core fix: adds realpathSync after path.resolve in normalizeWorkspaceId, with a try/catch fallback for non-existent paths. The logic is correct — empty/whitespace-only strings are guarded before any FS calls.
packages/server/src/server/workspace-directory.ts Both the exact-match and prefix-match branches now normalize workspace.cwd via normalizeWorkspaceId. The home-directory guard is also resolved through normalizeWorkspaceId(homedir()), fixing the asymmetry noted in a prior review thread. The bestMatch refactor to carry the pre-computed normalizedCwd length is correct.
packages/server/src/server/session.ts findExactWorkspaceByDirectory now normalizes workspace.cwd before comparing it against normalizedCwd. Both sides are consistently resolved through normalizePersistedWorkspaceId (an alias for normalizeWorkspaceId).
packages/server/src/server/workspace-registry-model.test.ts Four new tests cover the symlink-resolution, no-symlink, non-existent path, and empty-string cases. afterEach is used but not explicitly imported (works via globals: true, but inconsistent with the rest of the file's explicit imports).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Input: cwd (may be symlink)"] --> B["normalizeWorkspaceId(cwd)"]
    B --> C["cwd.trim() → empty?"]
    C -- "yes" --> D["return cwd unchanged"]
    C -- "no" --> E["path.resolve(trimmed) → absolute path"]
    E --> F["realpathSync(resolved)"]
    F -- "success" --> G["return real path (symlinks resolved)"]
    F -- "throws (non-existent)" --> H["return resolved (no-symlink fallback)"]
    G --> I["Comparison sites"]
    H --> I
    I --> J["workspace-directory.ts\nresolveRegisteredWorkspaceIdForCwd"]
    I --> K["session.ts\nfindExactWorkspaceByDirectory"]
    I --> L["workspace-directory.ts\nhomedir guard"]
Loading

Reviews (2): Last reviewed commit: "fix: correct realpathSync casing and nor..." | Re-trigger Greptile

Comment thread packages/server/src/server/workspace-registry-model.test.ts Outdated
Comment thread packages/server/src/server/workspace-directory.ts Outdated
- Fix realPathSync (capital P) to realpathSync in test — would cause
  ReferenceError at runtime
- Normalize homedir() through normalizeWorkspaceId for consistent
  symlink comparison in prefix match
@keepmind9
Copy link
Copy Markdown
Author

CI workflows are waiting for approval (status: action_required). Could a maintainer approve and run the workflows? Thanks!

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