Skip to content

Commit 8eb5503

Browse files
committed
fix(chat): surface archive errors on nested reads/greps instead of not-found
A nested archive read/grep ran findArchiveEntryRawPath outside the ArchiveError catch, so an invalid/too-many-entries archive escaped to the generic handler and showed as "Upload not found" (read) or a generic grep failure, while a bare archive read already surfaced the real reason. Widen the catch so both paths report the actual ArchiveError message.
1 parent d12d9da commit 8eb5503

2 files changed

Lines changed: 45 additions & 32 deletions

File tree

apps/sim/lib/copilot/tools/handlers/upload-file-reader.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,15 @@ describe('readChatUploadPath / listChatUploadArchiveEntries (archive)', () => {
280280
expect(result?.content).toContain('present.txt')
281281
})
282282

283+
it('surfaces an archive error on a nested read instead of null', async () => {
284+
mockOrderByThenLimit([makeRow({ displayName: 'bundle.zip', contentType: 'application/zip' })])
285+
mockFetchWorkspaceFileBuffer.mockResolvedValueOnce(Buffer.from('not a zip at all'))
286+
287+
const result = await readChatUploadPath('bundle.zip', 'entry.txt', CHAT_ID)
288+
289+
expect(result?.content).toContain('Not a valid .zip archive')
290+
})
291+
283292
it('rejects an oversized archive WITHOUT downloading it', async () => {
284293
mockOrderByThenLimit([
285294
makeRow({

apps/sim/lib/copilot/tools/handlers/upload-file-reader.ts

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -306,31 +306,32 @@ export async function listChatUploadArchiveEntries(
306306

307307
/**
308308
* Render one archive entry from the archive buffer with the same extraction
309-
* logic as a stored upload. Returns null when the entry is missing; returns a
310-
* placeholder result for cap violations.
309+
* logic as a stored upload. Returns null when the entry is genuinely missing;
310+
* returns a bracketed placeholder for any {@link ArchiveError} (invalid archive,
311+
* too many entries, oversized entry) — matching {@link buildArchiveManifest} so a
312+
* nested read surfaces the real reason instead of the VFS "Upload not found".
311313
*/
312314
async function readArchiveEntry(
313315
archiveBuffer: Buffer,
314316
entryPath: string
315317
): Promise<FileReadResult | null> {
316-
const rawPath = await findArchiveEntryRawPath(archiveBuffer, entryPath)
317-
if (!rawPath) return null
318-
let entryBuffer: Buffer | null
319318
try {
320-
entryBuffer = await extractArchiveEntry(archiveBuffer, rawPath)
319+
const rawPath = await findArchiveEntryRawPath(archiveBuffer, entryPath)
320+
if (!rawPath) return null
321+
const entryBuffer = await extractArchiveEntry(archiveBuffer, rawPath)
322+
if (!entryBuffer) return null
323+
const ext = getFileExtension(rawPath)
324+
return renderFileBuffer(entryBuffer, {
325+
name: rawPath,
326+
type: getMimeTypeFromExtension(ext),
327+
ext,
328+
})
321329
} catch (err) {
322330
if (err instanceof ArchiveError) {
323331
return { content: `[${err.message}]`, totalLines: 1 }
324332
}
325333
throw err
326334
}
327-
if (!entryBuffer) return null
328-
const ext = getFileExtension(rawPath)
329-
return renderFileBuffer(entryBuffer, {
330-
name: rawPath,
331-
type: getMimeTypeFromExtension(ext),
332-
ext,
333-
})
334335
}
335336

336337
/**
@@ -440,32 +441,35 @@ export async function grepChatUploadPath(
440441
)
441442
}
442443
const archiveBuffer = await fetchWorkspaceFileBuffer(record)
443-
const rawPath = await findArchiveEntryRawPath(archiveBuffer, entryPath)
444-
if (!rawPath) {
445-
throw new WorkspaceFileGrepError(
446-
`Archive entry not found: "${decodeEntryPath(entryPath)}" in "${record.name}".`
447-
)
448-
}
449-
let entryBuffer: Buffer | null
450444
try {
451-
entryBuffer = await extractArchiveEntry(archiveBuffer, rawPath)
445+
const rawPath = await findArchiveEntryRawPath(archiveBuffer, entryPath)
446+
if (!rawPath) {
447+
throw new WorkspaceFileGrepError(
448+
`Archive entry not found: "${decodeEntryPath(entryPath)}" in "${record.name}".`
449+
)
450+
}
451+
const entryBuffer = await extractArchiveEntry(archiveBuffer, rawPath)
452+
if (!entryBuffer) {
453+
throw new WorkspaceFileGrepError(
454+
`Archive entry not found: "${rawPath}" in "${record.name}".`
455+
)
456+
}
457+
const ext = getFileExtension(rawPath)
458+
const result = await renderFileBuffer(entryBuffer, {
459+
name: rawPath,
460+
type: getMimeTypeFromExtension(ext),
461+
ext,
462+
})
463+
const uploadsPath = `uploads/${encodeUploadName(record.name)}/${encodeEntryPath(rawPath)}`
464+
return grepReadResult(uploadsPath, result, pattern, uploadsPath, options)
452465
} catch (err) {
466+
// Surface archive failures (invalid/too-many/oversized) as a grep error
467+
// with the real reason rather than a generic internal failure.
453468
if (err instanceof ArchiveError) {
454469
throw new WorkspaceFileGrepError(err.message)
455470
}
456471
throw err
457472
}
458-
if (!entryBuffer) {
459-
throw new WorkspaceFileGrepError(`Archive entry not found: "${rawPath}" in "${record.name}".`)
460-
}
461-
const ext = getFileExtension(rawPath)
462-
const result = await renderFileBuffer(entryBuffer, {
463-
name: rawPath,
464-
type: getMimeTypeFromExtension(ext),
465-
ext,
466-
})
467-
const uploadsPath = `uploads/${encodeUploadName(record.name)}/${encodeEntryPath(rawPath)}`
468-
return grepReadResult(uploadsPath, result, pattern, uploadsPath, options)
469473
}
470474

471475
const result = await readFileRecord(record)

0 commit comments

Comments
 (0)