From 105262994efb9b18f37196b6f68751b392b41a5e Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Mon, 12 Oct 2026 10:59:09 -0700 Subject: [PATCH] fix(mothership): stop Chat reading files search as a download of a file named search The Chat file transport matched GET /api/v2/files/search as a direct download of file id 'search', so files search failed with an unrelated validation error. It now recognizes the search route and refuses it with a message pointing at grep --scope files, because search results quote file text without reporting file provenance and so cannot be admitted to Chat yet. --- .../mothership/agent-cli/file-provenance.test.ts | 9 +++++++++ .../mothership/agent-cli/file-read-transport.ts | 15 +++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/apps/sim/lib/mothership/agent-cli/file-provenance.test.ts b/apps/sim/lib/mothership/agent-cli/file-provenance.test.ts index 893d05af5e5..8ad6fb769c1 100644 --- a/apps/sim/lib/mothership/agent-cli/file-provenance.test.ts +++ b/apps/sim/lib/mothership/agent-cli/file-provenance.test.ts @@ -497,6 +497,15 @@ describe('file provenance at the actual CLI and model-result boundary', () => { ) }) + it('answers files search as a search, refused until its results report provenance', async () => { + const result = await runCli({ kind: 'cli', argv: ['files', 'search', '--query', 'CANARY'] }) + expect(result.success).toBe(false) + expect(JSON.stringify(result)).toContain('grep --scope files') + expect(mocks.reference).not.toHaveBeenCalled() + expect(mocks.file).not.toHaveBeenCalled() + expect(mocks.stream).not.toHaveBeenCalled() + }) + it('refuses an unknown file through the actual CLI text read without loading its bytes', async () => { classify('unknown') const result = await runCli(readFile) diff --git a/apps/sim/lib/mothership/agent-cli/file-read-transport.ts b/apps/sim/lib/mothership/agent-cli/file-read-transport.ts index 436ec74380d..d0ee33fd263 100644 --- a/apps/sim/lib/mothership/agent-cli/file-read-transport.ts +++ b/apps/sim/lib/mothership/agent-cli/file-read-transport.ts @@ -5,6 +5,7 @@ import { v2DownloadFileContract, v2ListFileFoldersContract, v2ReadFileTextContract, + v2SearchFileContentContract, } from '@/lib/api/contracts/v2/files' import { authenticateV2ApiKey, @@ -54,6 +55,8 @@ export function createFileReadTransport(context: { `${basePath}${v2ListFileFoldersContract.path}`, `${basePath}${v2BulkDownloadFilesContract.path}`, ]) + /** Search results quote file text but name no file provenance, so Chat cannot admit them. */ + const searchPath = `${basePath}${v2SearchFileContentContract.path}` /** * Admits one delivery's classification into the invocation registry. A delivery it refuses is * either withheld (text and forwarded responses reach only the model, so refusing them is enough) @@ -135,6 +138,18 @@ export function createFileReadTransport(context: { ) { return forward(input, init) } + if (url.pathname === searchPath) { + return Response.json( + { + error: { + code: 'FORBIDDEN', + message: + 'files search is unavailable in Chat. Search file contents with grep --scope files.', + }, + }, + { status: 403 } + ) + } const match = /^([^/]+)(\/text)?$/.exec(url.pathname.slice(prefix.length)) if (!match) return forward(input, init) const request = new NextRequest(new Request(input, init))