Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/core/src/core/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ These tools are also EXTREMELY helpful for planning tasks, and for breaking down
- **Proactiveness:** Fulfill the user's request thoroughly, including reasonable, directly implied follow-up actions.
${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.'}
- **Explaining Changes:** After completing a code modification or file operation *do not* provide summaries unless asked.
- **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.
- **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.
- **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.
- **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.

Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/tools/read-data-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ class ReadDataFileToolInvocation extends BaseToolInvocation<
// Format the output for the LLM
const llmContent = `
# Data File: ${relativePath}
(Absolute Path on this device: ${filePath})

## File Information
- **Type**: ${result.fileType}
Expand Down Expand Up @@ -474,7 +475,7 @@ export class ReadDataFileTool extends BaseDeclarativeTool<
properties: {
absolute_path: {
description:
"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.",
"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.",
type: 'string',
},
max_rows: {
Expand Down
108 changes: 108 additions & 0 deletions packages/core/src/tools/workspace-error-helper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import { describe, it, expect } from 'vitest';
import { generateWorkspacePathError } from './workspace-error-helper.js';

describe('generateWorkspacePathError', () => {
const workspaceDirs = ['/Users/user/project'];

it('should return a standard error message for normal paths outside workspace', () => {
const filePath = '/etc/passwd';
const result = generateWorkspacePathError(filePath, workspaceDirs);

expect(result).toContain('File path is outside the workspace');
expect(result).toContain(`Requested path: ${filePath}`);
expect(result).not.toContain('ERROR: Path Hallucination Detected');
});

it('should detect hallucinated path for "suyashpradhan"', () => {
const filePath = '/Users/suyashpradhan/Desktop/project/file.ts';
const result = generateWorkspacePathError(filePath, workspaceDirs);

expect(result).toContain('ERROR: Path Hallucination Detected');
expect(result).toContain('CRITICAL: You are attempting to use a path from your training data');
expect(result).toContain('Your current PROJECT ROOT is: /Users/user/project');
});

it('should detect generic unknown user paths (e.g., "sarah") as hallucination', () => {
const filePath = '/Users/sarah/Documents/data.csv';
const result = generateWorkspacePathError(filePath, workspaceDirs);

expect(result).toContain('ERROR: Path Hallucination Detected');
});

it('should detect Windows unknown user paths as hallucination', () => {
const winWorkspace = ['C:\\Users\\Eli\\Project'];
const filePath = 'C:\\Users\\John\\Desktop\\file.txt';
const result = generateWorkspacePathError(filePath, winWorkspace);

expect(result).toContain('ERROR: Path Hallucination Detected');
});

it('should detect Linux unknown user paths as hallucination', () => {
const linuxWorkspace = ['/home/ubuntu/project'];
const filePath = '/home/suyash/data.csv';
const result = generateWorkspacePathError(filePath, linuxWorkspace);

expect(result).toContain('ERROR: Path Hallucination Detected');
});

it('should NOT flag a hallucination if the project is in a folder named after a friend', () => {
// Scenario: User is 'eli', but has a project in a folder called 'richard_project'
const workspace = ['/Users/eli/Documents/richard_project'];
const filePath = '/Users/eli/Documents/richard_project/data.csv';

// This is just a normal out-of-workspace error (or valid if inside)
// But importantly, it's NOT a hallucination of the 'richard' user.
const result = generateWorkspacePathError(filePath, workspace);
expect(result).not.toContain('ERROR: Path Hallucination Detected');
});

it('should NOT flag system paths (like /tmp) as hallucinations', () => {
const workspace = ['/Users/user/project'];
const filePath = '/tmp/debug.log';
const result = generateWorkspacePathError(filePath, workspace);

expect(result).toContain('File path is outside the workspace');
expect(result).not.toContain('ERROR: Path Hallucination Detected');
});

it('should detect hallucinated path for "blackbox-cli"', () => {
const filePath = '/some/path/blackbox-cli/package.json';
const result = generateWorkspacePathError(filePath, workspaceDirs);

expect(result).toContain('ERROR: Path Hallucination Detected');
});

it('should detect hallucinated data files (CSV) from other user directories', () => {
// Scenario: AI tries to use read_data_file on a CSV in a hallucinated folder
const filePath = '/Users/suyashpradhan/Downloads/subscriptions.csv';
const result = generateWorkspacePathError(filePath, workspaceDirs);

expect(result).toContain('ERROR: Path Hallucination Detected');
expect(result).toContain('Requested path: /Users/suyashpradhan/Downloads/subscriptions.csv');
});

it('should detect hallucinated paths when the AI tries to write a file elsewhere', () => {
// Scenario: AI tries to use write_file to a different user's desktop
// We'll use a generic name like "Richard" which I've now generalized detection for
const filePath = 'C:\\Users\\Richard\\Desktop\\output.txt';
const winWorkspace = ['C:\\Users\\User\\Project'];
const result = generateWorkspacePathError(filePath, winWorkspace);

expect(result).toContain('ERROR: Path Hallucination Detected');
});

it('should NOT detect "suyashpradhan" as hallucination if it is part of the actual workspace', () => {
const realWorkspace = ['/Users/suyashpradhan/my-docs'];
const filePath = '/Users/suyashpradhan/other-file.ts';
const result = generateWorkspacePathError(filePath, realWorkspace);

expect(result).toContain('File path is outside the workspace');
expect(result).not.toContain('ERROR: Path Hallucination Detected');
});
});
35 changes: 35 additions & 0 deletions packages/core/src/tools/workspace-error-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,41 @@ export function generateWorkspacePathError(
filePath: string,
workspaceDirectories: readonly string[],
): string {
// GENERIC CHECK: Detect if the path looks like it belongs to a different user profile.
// This catches hallucinations for ANY username on Windows, macOS, and Linux.
// Patterns: C:\Users\name, /Users/name, /home/name
const userDirPattern = /^([a-zA-Z]:\\Users\\[^\\]+)|^\/Users\/[^\/]+|^\/home\/[^\/]+/i;
const pathMatch = filePath.match(userDirPattern);

let isHallucinatedPath = false;

if (pathMatch) {
const matchedUserDir = pathMatch[0].toLowerCase();
// We check if the matched "User Directory" exists anywhere in the valid workspace paths.
// This allows projects to be located deep within a user's home directory.
const isMatchedToRealUser = workspaceDirectories.some(dir =>
dir.toLowerCase().includes(matchedUserDir)
);

if (!isMatchedToRealUser) {
isHallucinatedPath = true;
}
}

// Fallback for specific known training data artifacts
if (!isHallucinatedPath && /blackbox-cli/i.test(filePath)) {
isHallucinatedPath = true;
}

if (isHallucinatedPath) {
return `ERROR: Path Hallucination Detected.

Requested path: ${filePath}
Your current PROJECT ROOT is: ${workspaceDirectories.join(', ')}

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.`;
}

return `File path is outside the workspace

Requested path: ${filePath}
Expand Down
7 changes: 5 additions & 2 deletions packages/core/src/utils/environmentContext.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('getDirectoryContextString', () => {
it('should return context string for a single directory', async () => {
const contextString = await getDirectoryContextString(mockConfig as Config);
expect(contextString).toContain(
"I'm currently working in the directory: /test/dir",
"PROJECT ROOT DIRECTORY (Use this for all absolute paths): /test/dir",
);
expect(contextString).toContain(
'Here is the folder structure of the current working directories:\n\nMock Folder Structure',
Expand Down Expand Up @@ -119,11 +119,14 @@ describe('getEnvironmentContext', () => {
expect(context).toContain("(formatted according to the user's locale)");
expect(context).toContain(`My operating system is: ${process.platform}`);
expect(context).toContain(
"I'm currently working in the directory: /test/dir",
"PROJECT ROOT DIRECTORY (Use this for all absolute paths): /test/dir",
);
expect(context).toContain(
'Here is the folder structure of the current working directories:\n\nMock Folder Structure',
);
expect(context).toContain(
'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.',
);
expect(getFolderStructure).toHaveBeenCalledWith('/test/dir', {
fileService: undefined,
});
Expand Down
5 changes: 4 additions & 1 deletion packages/core/src/utils/environmentContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ export async function getDirectoryContextString(

let workingDirPreamble: string;
if (workspaceDirectories.length === 1) {
workingDirPreamble = `I'm currently working in the directory: ${workspaceDirectories[0]}`;
// Labeling this as PROJECT ROOT to provide a clear anchor for absolute path construction.
workingDirPreamble = `PROJECT ROOT DIRECTORY (Use this for all absolute paths): ${workspaceDirectories[0]}`;
} else {
const dirList = workspaceDirectories.map((dir) => ` - ${dir}`).join('\n');
workingDirPreamble = `I'm currently working in the following directories:\n${dirList}`;
Expand Down Expand Up @@ -65,6 +66,8 @@ This is the Blackbox Code. We are setting up the context for our chat.
Today's date is ${today} (formatted according to the user's locale).
My operating system is: ${platform}
${directoryContext}

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.
`.trim();

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