{"record":{"id":"a8af4c44afdb9142","repo":"RocketChat/Rocket.Chat","slug":"invalid-file","errorCode":"invalid-file","errorMessage":"invalid-file","messagePattern":"invalid-file","errorType":"exception","errorClass":"Meteor.Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/api/v1/rooms.ts","lineNumber":338,"sourceCode":"\t\t\t\t},\n\t\t\t});\n\t\t},\n\t},\n);\n\nAPI.v1.addRoute(\n\t'rooms.mediaConfirm/:rid/:fileId',\n\t{ authRequired: true },\n\t{\n\t\tasync post() {\n\t\t\tif (!(await canAccessRoomIdAsync(this.urlParams.rid, this.userId))) {\n\t\t\t\treturn API.v1.forbidden();\n\t\t\t}\n\n\t\t\tconst file = await Uploads.findOneByIdAndUserIdAndRoomId(this.urlParams.fileId, this.userId, this.urlParams.rid);\n\n\t\t\tif (!file) {\n\t\t\t\tthrow new Meteor.Error('invalid-file');\n\t\t\t}\n\n\t\t\tif ((this.bodyParams.description?.length ?? 0) > settings.get<number>('Message_MaxAllowedSize')) {\n\t\t\t\tthrow new Meteor.Error('error-message-size-exceeded');\n\t\t\t}\n\n\t\t\tfile.description = this.bodyParams.description;\n\t\t\tdelete this.bodyParams.description;\n\n\t\t\tif (this.bodyParams.fileName) {\n\t\t\t\tfile.name = this.bodyParams.fileName;\n\t\t\t\tdelete this.bodyParams.fileName;\n\t\t\t}\n\n\t\t\tif (this.bodyParams.fileContent) {\n\t\t\t\tfile.content = this.bodyParams.fileContent;\n\t\t\t\tdelete this.bodyParams.fileContent;\n\t\t\t}","sourceCodeStart":320,"sourceCodeEnd":356,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/f9d3ec372bb580fa8d036f94cf03925a478ef768/apps/meteor/server/api/v1/rooms.ts#L320-L356","documentation":"Thrown by POST rooms.mediaConfirm/:rid/:fileId when no Uploads record matches the fileId, the calling user, AND the room simultaneously. The two-phase (Stream) upload flow stores the binary first and confirms later; this error means the confirmation step can't find that stored upload.","triggerScenarios":"POST /api/v1/rooms.mediaConfirm/:rid/:fileId where the fileId was never created, belongs to a different user, belongs to a different room, or was already consumed/cleaned up before confirmation.","commonSituations":"Calling mediaConfirm before the upload phase completed; using a fileId from a different session/user; replaying a confirm after the upload expired; race with upload cleanup; wrong rid in the URL.","solutions":["Make sure the upload phase (rooms.upload / the Streamer/UFS flow) returned a fileId before calling mediaConfirm.","Pass the same rid and the authenticated user that created the upload.","Confirm promptly — do not cache fileIds across long-running sessions or user switches.","If this fires, re-run the full two-phase upload from scratch."],"exampleFix":"// before\nawait rest.post(`/api/v1/rooms.mediaConfirm/${rid}/${guessedFileId}`);\n\n// after\nconst start = await startUploadPhase(rid, file); // returns fileId\nawait rest.post(`/api/v1/rooms.mediaConfirm/${rid}/${start.fileId}`, {\n  description, fileName, fileContent,\n});","handlingStrategy":"validation","validationCode":"if (!fileId || typeof fileId !== 'string') {\n  throw new Error('mediaConfirm requires a fileId from the upload phase');\n}","typeGuard":"function isUploadRef(x: unknown): x is { fileId: string; rid: string } {\n  return !!x && typeof (x as any).fileId === 'string' && typeof (x as any).rid === 'string';\n}","tryCatchPattern":"try {\n  await rest.post(`/api/v1/rooms.mediaConfirm/${rid}/${fileId}`, body);\n} catch (e) {\n  if (isMeteorError(e, 'invalid-file')) {\n    // restart the full two-phase upload\n  } else throw e;\n}","preventionTips":["Always confirm with the same user and rid that created the upload.","Confirm promptly; don't cache fileIds across sessions.","Treat mediaConfirm as strictly paired with its upload phase."],"tags":["file-upload","two-phase-upload","rest-api","rooms"],"backgroundTag":null,"analyzedSha":"f9d3ec372bb580fa8d036f94cf03925a478ef768","analyzedAt":"2026-08-12T19:07:17.372Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}