{"record":{"id":"ddb75fac346d1c5b","repo":"ruvnet/ruflo","slug":"file-not-found","errorCode":null,"errorMessage":"File not found","messagePattern":"File not found","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ruflo/src/ruvocal/src/lib/server/database/postgres.ts","lineNumber":668,"sourceCode":"\t\t\t\t\t\tdata,\n\t\t\t\t\t\tJSON.stringify(options?.metadata ?? {}),\n\t\t\t\t\t]\n\t\t\t\t);\n\t\t\t},\n\t\t};\n\t}\n\n\topenDownloadStream(id: ObjectId | string) {\n\t\tconst fileId = typeof id === \"string\" ? id : id.toString();\n\t\t// Return a readable-like object\n\t\treturn {\n\t\t\tasync toArray(): Promise<Buffer[]> {\n\t\t\t\tconst pool = getPool();\n\t\t\t\tconst result = await pool.query(\n\t\t\t\t\t`SELECT data FROM files WHERE _id = $1`,\n\t\t\t\t\t[fileId]\n\t\t\t\t);\n\t\t\t\tif (result.rows.length === 0) throw new Error(\"File not found\");\n\t\t\t\treturn [result.rows[0].data];\n\t\t\t},\n\t\t};\n\t}\n\n\tasync delete(id: ObjectId | string) {\n\t\tconst fileId = typeof id === \"string\" ? id : id.toString();\n\t\tconst pool = getPool();\n\t\tawait pool.query(`DELETE FROM files WHERE _id = $1`, [fileId]);\n\t}\n\n\tasync find(filter: Record<string, unknown> = {}) {\n\t\tconst w = filterToWhere(filter);\n\t\tconst pool = getPool();\n\t\tconst result = await pool.query(\n\t\t\t`SELECT _id, filename, content_type, length, metadata, created_at FROM files WHERE ${w.text}`,\n\t\t\tw.values\n\t\t);","sourceCodeStart":650,"sourceCodeEnd":686,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/ruflo/src/ruvocal/src/lib/server/database/postgres.ts#L650-L686","documentation":"Thrown by the Postgres bucket's openDownloadStream().toArray() when SELECT data FROM files WHERE _id = $1 returns zero rows — the requested file id is not present in the files table. This is the Postgres-backed storage adapter (used when MONGODB_URL is not set and a Postgres pool is configured) for the chat UI's file/attachment storage.","triggerScenarios":"Downloading a file whose id has no row in the postgres files table: a stale/incorrect id, a file that expired or was deleted, a file stored in a different backend (e.g. still in Mongo) after a partial migration, or an id from another environment.","commonSituations":"Migrating from Mongo to Postgres left some files behind; a TTL/cleanup job removed the row; the client cached an old id; the upload failed silently so the row was never written; a cross-environment id leak (test id used in prod).","solutions":["Verify the file id against the files table and confirm the upload completed.","If migrating, run the backfill so every referenced id exists in Postgres.","Re-upload the file to generate a fresh id.","Return a 404 to the client and trigger re-upload rather than letting the throw propagate."],"exampleFix":"// before\nconst stream = bucket.openDownloadStream(id);\nconst buf = (await stream.toArray())[0]; // throws if missing\n\n// after\nconst res = await pool.query('SELECT data FROM files WHERE _id = $1', [id]);\nif (res.rows.length === 0) return res404('file not found');\nconst buf = res.rows[0].data;","handlingStrategy":"try-catch","validationCode":"async function fileExists(id: string): Promise<boolean> { const r = await pool.query('SELECT 1 FROM files WHERE _id = $1', [String(id)]); return r.rows.length > 0; }","typeGuard":"async function hasFileRow(id: string): Promise<boolean> { const r = await pool.query('SELECT 1 FROM files WHERE _id = $1', [String(id)]); return r.rows.length > 0; }","tryCatchPattern":"try { return await bucket.openDownloadStream(id).toArray(); } catch (e) { if ((e as Error).message === 'File not found') return res.status(404).json({ error: 'file not found' }); throw e; }","preventionTips":["Backfill files when migrating Mongo → Postgres.","Confirm uploads committed a row before referencing the id.","Return 404 and let the client re-upload rather than propagating the throw."],"tags":["database","postgres","file-storage","ruvocal"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}