Skip to content

Commit fbea3ac

Browse files
feat: implement generic path hallucination detection and project root anchoring
- Added dynamic PROJECT ROOT labeling in environment context. - Implemented generic path hallucination filter in workspace error handling. - Removed hardcoded developer paths and replaced with generic placeholders. - Added comprehensive tests for Windows, Mac, and Linux path scenarios.
1 parent a5ab039 commit fbea3ac

6 files changed

Lines changed: 136 additions & 5 deletions

File tree

packages/core/src/core/prompts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ These tools are also EXTREMELY helpful for planning tasks, and for breaking down
395395
- **Proactiveness:** Fulfill the user's request thoroughly, including reasonable, directly implied follow-up actions.
396396
${isNonInteractive ? '' :'- **Confirm Ambiguity/Expansion:** Do not take significant actions beyond the clear scope of the request without confirming with the user. If asked *how* to do something, explain first, don\'t just do it.'}
397397
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
398-
- **Path Construction:** Before using any file system tool (e.g., ${ToolNames.READ_FILE}' or '${ToolNames.WRITE_FILE}'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path.
398+
- **Path Construction:** Before using any file system tool (e.g., '${ToolNames.READ_FILE}' or '${ToolNames.WRITE_FILE}'), you must construct the full absolute path for the file_path argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. **CRITICAL: NEVER hallucinate paths from other environments or users (e.g., paths containing 'suyashpradhan', etc.). Use ONLY the project root directory provided in your context.** If the user provides a relative path, you must resolve it against the root directory to create an absolute path.
399399
- **Do Not revert changes:** Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes.
400400
- **Verification & Testing:** You should verify that the changes done by you does not introduct any errors by building / compiling the changes. Then doing functional testing of the changes to ensure the implementation is successful and working accurately.
401401

packages/core/src/tools/read-data-file.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ class ReadDataFileToolInvocation extends BaseToolInvocation<
411411
// Format the output for the LLM
412412
const llmContent = `
413413
# Data File: ${relativePath}
414+
(Absolute Path on this device: ${filePath})
414415
415416
## File Information
416417
- **Type**: ${result.fileType}
@@ -474,7 +475,7 @@ export class ReadDataFileTool extends BaseDeclarativeTool<
474475
properties: {
475476
absolute_path: {
476477
description:
477-
"The absolute path to the data file to read and parse (e.g., '/home/user/project/data.csv'). Supported file types: .csv, .json, .txt, .xlsx, .xls, .docx, .doc. Relative paths are not supported.",
478+
"The absolute path to the data file to read and parse (e.g., '<PROJECT_ROOT>/data.csv'). Supported file types: .csv, .json, .txt, .xlsx, .xls, .docx, .doc. Relative paths are not supported.",
478479
type: 'string',
479480
},
480481
max_rows: {
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import { describe, it, expect } from 'vitest';
8+
import { generateWorkspacePathError } from './workspace-error-helper.js';
9+
10+
describe('generateWorkspacePathError', () => {
11+
const workspaceDirs = ['/Users/user/project'];
12+
13+
it('should return a standard error message for normal paths outside workspace', () => {
14+
const filePath = '/etc/passwd';
15+
const result = generateWorkspacePathError(filePath, workspaceDirs);
16+
17+
expect(result).toContain('File path is outside the workspace');
18+
expect(result).toContain(`Requested path: ${filePath}`);
19+
expect(result).not.toContain('ERROR: Path Hallucination Detected');
20+
});
21+
22+
it('should detect hallucinated path for "suyashpradhan"', () => {
23+
const filePath = '/Users/suyashpradhan/Desktop/project/file.ts';
24+
const result = generateWorkspacePathError(filePath, workspaceDirs);
25+
26+
expect(result).toContain('ERROR: Path Hallucination Detected');
27+
expect(result).toContain('CRITICAL: You are attempting to use a path from your training data');
28+
expect(result).toContain('Your current PROJECT ROOT is: /Users/user/project');
29+
});
30+
31+
it('should detect generic unknown user paths (e.g., "sarah") as hallucination', () => {
32+
const filePath = '/Users/sarah/Documents/data.csv';
33+
const result = generateWorkspacePathError(filePath, workspaceDirs);
34+
35+
expect(result).toContain('ERROR: Path Hallucination Detected');
36+
});
37+
38+
it('should detect Windows unknown user paths as hallucination', () => {
39+
const winWorkspace = ['C:\\Users\\Eli\\Project'];
40+
const filePath = 'C:\\Users\\John\\Desktop\\file.txt';
41+
const result = generateWorkspacePathError(filePath, winWorkspace);
42+
43+
expect(result).toContain('ERROR: Path Hallucination Detected');
44+
});
45+
46+
it('should detect Linux unknown user paths as hallucination', () => {
47+
const linuxWorkspace = ['/home/ubuntu/project'];
48+
const filePath = '/home/suyash/data.csv';
49+
const result = generateWorkspacePathError(filePath, linuxWorkspace);
50+
51+
expect(result).toContain('ERROR: Path Hallucination Detected');
52+
});
53+
54+
it('should NOT flag a hallucination if the project is in a folder named after a friend', () => {
55+
// Scenario: User is 'eli', but has a project in a folder called 'richard_project'
56+
const workspace = ['/Users/eli/Documents/richard_project'];
57+
const filePath = '/Users/eli/Documents/richard_project/data.csv';
58+
59+
// This is just a normal out-of-workspace error (or valid if inside)
60+
// But importantly, it's NOT a hallucination of the 'richard' user.
61+
const result = generateWorkspacePathError(filePath, workspace);
62+
expect(result).not.toContain('ERROR: Path Hallucination Detected');
63+
});
64+
65+
it('should NOT flag system paths (like /tmp) as hallucinations', () => {
66+
const workspace = ['/Users/user/project'];
67+
const filePath = '/tmp/debug.log';
68+
const result = generateWorkspacePathError(filePath, workspace);
69+
70+
expect(result).toContain('File path is outside the workspace');
71+
expect(result).not.toContain('ERROR: Path Hallucination Detected');
72+
});
73+
74+
it('should detect hallucinated path for "blackbox-cli"', () => {
75+
const filePath = '/some/path/blackbox-cli/package.json';
76+
const result = generateWorkspacePathError(filePath, workspaceDirs);
77+
78+
expect(result).toContain('ERROR: Path Hallucination Detected');
79+
});
80+
81+
it('should NOT detect "suyashpradhan" as hallucination if it is part of the actual workspace', () => {
82+
const realWorkspace = ['/Users/suyashpradhan/my-docs'];
83+
const filePath = '/Users/suyashpradhan/other-file.ts';
84+
const result = generateWorkspacePathError(filePath, realWorkspace);
85+
86+
expect(result).toContain('File path is outside the workspace');
87+
expect(result).not.toContain('ERROR: Path Hallucination Detected');
88+
});
89+
});

packages/core/src/tools/workspace-error-helper.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,41 @@ export function generateWorkspacePathError(
1111
filePath: string,
1212
workspaceDirectories: readonly string[],
1313
): string {
14+
// GENERIC CHECK: Detect if the path looks like it belongs to a different user profile.
15+
// This catches hallucinations for ANY username on Windows, macOS, and Linux.
16+
// Patterns: C:\Users\name, /Users/name, /home/name
17+
const userDirPattern = /^([a-zA-Z]:\\Users\\[^\\]+)|^\/Users\/[^\/]+|^\/home\/[^\/]+/i;
18+
const pathMatch = filePath.match(userDirPattern);
19+
20+
let isHallucinatedPath = false;
21+
22+
if (pathMatch) {
23+
const matchedUserDir = pathMatch[0].toLowerCase();
24+
// We check if the matched "User Directory" exists anywhere in the valid workspace paths.
25+
// This allows projects to be located deep within a user's home directory.
26+
const isMatchedToRealUser = workspaceDirectories.some(dir =>
27+
dir.toLowerCase().includes(matchedUserDir)
28+
);
29+
30+
if (!isMatchedToRealUser) {
31+
isHallucinatedPath = true;
32+
}
33+
}
34+
35+
// Fallback for specific known training data artifacts
36+
if (!isHallucinatedPath && /blackbox-cli/i.test(filePath)) {
37+
isHallucinatedPath = true;
38+
}
39+
40+
if (isHallucinatedPath) {
41+
return `ERROR: Path Hallucination Detected.
42+
43+
Requested path: ${filePath}
44+
Your current PROJECT ROOT is: ${workspaceDirectories.join(', ')}
45+
46+
CRITICAL: You are attempting to use a path from your training data or another user's environment. This path DOES NOT EXIST on this machine. Please reconstruct the path using the correct PROJECT ROOT mentioned above.`;
47+
}
48+
1449
return `File path is outside the workspace
1550
1651
Requested path: ${filePath}

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ describe('getDirectoryContextString', () => {
4646
it('should return context string for a single directory', async () => {
4747
const contextString = await getDirectoryContextString(mockConfig as Config);
4848
expect(contextString).toContain(
49-
"I'm currently working in the directory: /test/dir",
49+
"PROJECT ROOT DIRECTORY (Use this for all absolute paths): /test/dir",
5050
);
5151
expect(contextString).toContain(
5252
'Here is the folder structure of the current working directories:\n\nMock Folder Structure',
@@ -119,11 +119,14 @@ describe('getEnvironmentContext', () => {
119119
expect(context).toContain("(formatted according to the user's locale)");
120120
expect(context).toContain(`My operating system is: ${process.platform}`);
121121
expect(context).toContain(
122-
"I'm currently working in the directory: /test/dir",
122+
"PROJECT ROOT DIRECTORY (Use this for all absolute paths): /test/dir",
123123
);
124124
expect(context).toContain(
125125
'Here is the folder structure of the current working directories:\n\nMock Folder Structure',
126126
);
127+
expect(context).toContain(
128+
'IMPORTANT: All file paths you use MUST be relative to the PROJECT ROOT DIRECTORY or absolute paths starting with it. NEVER hallucinate paths from other environments.',
129+
);
127130
expect(getFolderStructure).toHaveBeenCalledWith('/test/dir', {
128131
fileService: undefined,
129132
});

packages/core/src/utils/environmentContext.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ export async function getDirectoryContextString(
3131

3232
let workingDirPreamble: string;
3333
if (workspaceDirectories.length === 1) {
34-
workingDirPreamble = `I'm currently working in the directory: ${workspaceDirectories[0]}`;
34+
// Labeling this as PROJECT ROOT to provide a clear anchor for absolute path construction.
35+
workingDirPreamble = `PROJECT ROOT DIRECTORY (Use this for all absolute paths): ${workspaceDirectories[0]}`;
3536
} else {
3637
const dirList = workspaceDirectories.map((dir) => ` - ${dir}`).join('\n');
3738
workingDirPreamble = `I'm currently working in the following directories:\n${dirList}`;
@@ -65,6 +66,8 @@ This is the Blackbox Code. We are setting up the context for our chat.
6566
Today's date is ${today} (formatted according to the user's locale).
6667
My operating system is: ${platform}
6768
${directoryContext}
69+
70+
IMPORTANT: All file paths you use MUST be relative to the PROJECT ROOT DIRECTORY or absolute paths starting with it. NEVER hallucinate paths from other environments.
6871
`.trim();
6972

7073
const initialParts: Part[] = [{ text: context }];

0 commit comments

Comments
 (0)