lobehub/lobehub · error · TRPCError
NOT_FOUND
NOT_FOUND
Error message
File "${input.fileId}" not found. What it means
Thrown by resolveAudio (called from transcribe) when input.fileId is set but FileModel.findById returns null. The FileModel is constructed userId-scoped, so findById only finds files owned by the caller — a file belonging to another user is indistinguishable from a non-existent one. This fires before any S3 access is attempted.
Source
Thrown at apps/server/src/routers/lambda/asr.ts:157
}),
});
/**
* Turn the request into raw audio bytes + metadata, from either a stored file
* (downloaded from S3, ownership enforced by the userId-scoped FileModel) or the
* inline base64 payload.
*/
async function resolveAudio(
ctx: { serverDB: LobeChatDatabase; userId: string },
input: { audioBase64?: string; fileId?: string; fileName?: string; mimeType?: string },
workspaceId?: string,
): Promise<ResolvedAudio> {
if (input.fileId) {
const fileModel = new FileModel(ctx.serverDB, ctx.userId, workspaceId);
const fileItem = await fileModel.findById(input.fileId);
if (!fileItem) {
throw new TRPCError({ code: 'NOT_FOUND', message: `File "${input.fileId}" not found.` });
}
const fileService = new FileService(ctx.serverDB, ctx.userId, workspaceId);
let bytes: Uint8Array;
try {
bytes = await fileService.getFileByteArray(fileItem.url);
} catch (error) {
if ((error as { Code?: string }).Code === 'NoSuchKey') {
throw new TRPCError({
code: 'NOT_FOUND',
message: `File "${input.fileId}" is no longer available in storage.`,
});
}
throw error;
}
return { bytes, fileName: fileItem.name, mimeType: fileItem.fileType };
}View on GitHub (pinned to 10f24d7ade)
Solutions
- Re-upload the audio file via the file upload endpoint and use the freshly returned fileId.
- Verify the fileId was created under the same userId (and workspaceId, if applicable) as the transcribe call.
- Check the file still exists via the file API before passing it to transcribe.
- Ensure the audio was uploaded through the same environment (dev vs prod) as the transcription request.
Example fix
// before: stale or foreign fileId
await asr.transcribe({ fileId: 'old-or-wrong-id', model: 'whisper-1' }); // -> NOT_FOUND
// after: re-upload and use fresh id
const { id } = await uploadAudioFile(file);
await asr.transcribe({ fileId: id, model: 'whisper-1' }); Defensive patterns
Strategy: validation
Validate before calling
// Verify the file exists and is owned by the caller before transcribing
const file = await fileRouter.getFile.query({ id: input.fileId });
if (!file) {
throw new Error('File not found or not owned by caller; re-upload');
}
await asrRouter.transcribe.mutate(input); Type guard
const isExistingFile = (
file: { id: string; userId: string } | null | undefined,
userId: string
): file is { id: string; userId: string } =>
!!file && file.userId === userId; Try / catch
try {
await asrRouter.transcribe.mutate(input);
} catch (e) {
if (e.shape?.data?.code === 'NOT_FOUND' && /File.*not found/.test(e.message)) {
// prompt re-upload
} else throw e;
} Prevention
- Always upload the audio via the file API immediately before transcribing; avoid caching fileIds.
- Ensure the file upload and transcription use the same userId/workspaceId.
- Filter file pickers to files owned by the current user to avoid cross-user id leakage.
When it happens
Trigger: Calling transcribe with a fileId that does not exist in the files table, or exists but belongs to a different userId/workspace. The query at FileModel.findById(input.fileId) returns null because of the ownership scoping.
Common situations: Stale fileId from a deleted file. Cross-user/cross-workspace fileId leak. A typo or truncated id. File uploaded to a different workspace than the one the transcription call is scoped to.
Related errors
AI-assisted analysis of lobehub/lobehub@10f24d7ade (2026-08-12).
Data as JSON: /api/errors/ca4bfcf25b0d55f9.
Report an issue: GitHub.