Skip to content

Commit 5ff77ca

Browse files
fix(core): preserve full path in workspace validation for non-existent segments
When realpathSync fails with ENOENT on non-existent paths, Node's error object (e.path) only contains the path up to the last existing segment, truncating the remaining portions. This causes incorrect path resolution on UNC paths such as WSL (\wsl.localhost\...) where deeply nested non-existent file paths lose their trailing segments. Append the remaining non-existent segments from the original resolved path to the prefix returned by realpathSync, ensuring the full intended path is preserved for workspace boundary checks. Fixes #18594 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d012929 commit 5ff77ca

2 files changed

Lines changed: 36 additions & 3 deletions

File tree

packages/core/src/utils/workspaceContext.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,30 @@ describe('WorkspaceContext with real filesystem', () => {
162162
);
163163
});
164164

165+
it('should accept deeply nested non-existent paths within workspace', () => {
166+
const workspaceContext = new WorkspaceContext(cwd);
167+
const deepPath = path.join(
168+
cwd,
169+
'nonexistent',
170+
'deeply',
171+
'nested',
172+
'file.txt',
173+
);
174+
expect(workspaceContext.isPathWithinWorkspace(deepPath)).toBe(true);
175+
});
176+
177+
it('should reject deeply nested non-existent paths outside workspace', () => {
178+
const workspaceContext = new WorkspaceContext(cwd);
179+
const deepPath = path.join(
180+
tempDir,
181+
'outside',
182+
'deeply',
183+
'nested',
184+
'file.txt',
185+
);
186+
expect(workspaceContext.isPathWithinWorkspace(deepPath)).toBe(false);
187+
});
188+
165189
describe.skipIf(os.platform() === 'win32')('with symbolic link', () => {
166190
describe('in the workspace', () => {
167191
let realDir: string;

packages/core/src/utils/workspaceContext.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,9 @@ export class WorkspaceContext {
227227
* if it did exist.
228228
*/
229229
private fullyResolvedPath(pathToCheck: string): string {
230+
const fullPath = path.resolve(this.targetDir, pathToCheck);
230231
try {
231-
return fs.realpathSync(path.resolve(this.targetDir, pathToCheck));
232+
return fs.realpathSync(fullPath);
232233
} catch (e: unknown) {
233234
if (
234235
isNodeError(e) &&
@@ -238,8 +239,16 @@ export class WorkspaceContext {
238239
// non-existent files.
239240
!this.isFileSymlink(e.path)
240241
) {
241-
// If it doesn't exist, e.path contains the fully resolved path.
242-
return e.path;
242+
// e.path only contains the path up to the last existing segment,
243+
// which truncates the rest on UNC paths (e.g. WSL \\wsl.localhost\...).
244+
// Append the remaining non-existent segments from the original path
245+
// to preserve the full intended path.
246+
const resolvedPrefix = e.path;
247+
const remainder = fullPath.substring(resolvedPrefix.length);
248+
if (remainder) {
249+
return resolvedPrefix + remainder;
250+
}
251+
return resolvedPrefix;
243252
}
244253
throw e;
245254
}

0 commit comments

Comments
 (0)