ruvnet/ruflo · error · Error

File not found

Error message

File not found

What it means

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.

Source

Thrown at ruflo/src/ruvocal/src/lib/server/database/postgres.ts:668

						data,
						JSON.stringify(options?.metadata ?? {}),
					]
				);
			},
		};
	}

	openDownloadStream(id: ObjectId | string) {
		const fileId = typeof id === "string" ? id : id.toString();
		// Return a readable-like object
		return {
			async toArray(): Promise<Buffer[]> {
				const pool = getPool();
				const result = await pool.query(
					`SELECT data FROM files WHERE _id = $1`,
					[fileId]
				);
				if (result.rows.length === 0) throw new Error("File not found");
				return [result.rows[0].data];
			},
		};
	}

	async delete(id: ObjectId | string) {
		const fileId = typeof id === "string" ? id : id.toString();
		const pool = getPool();
		await pool.query(`DELETE FROM files WHERE _id = $1`, [fileId]);
	}

	async find(filter: Record<string, unknown> = {}) {
		const w = filterToWhere(filter);
		const pool = getPool();
		const result = await pool.query(
			`SELECT _id, filename, content_type, length, metadata, created_at FROM files WHERE ${w.text}`,
			w.values
		);

View on GitHub (pinned to 6b01dc5a68)

Solutions

  1. Verify the file id against the files table and confirm the upload completed.
  2. If migrating, run the backfill so every referenced id exists in Postgres.
  3. Re-upload the file to generate a fresh id.
  4. Return a 404 to the client and trigger re-upload rather than letting the throw propagate.

Example fix

// before
const stream = bucket.openDownloadStream(id);
const buf = (await stream.toArray())[0]; // throws if missing

// after
const res = await pool.query('SELECT data FROM files WHERE _id = $1', [id]);
if (res.rows.length === 0) return res404('file not found');
const buf = res.rows[0].data;
Defensive patterns

Strategy: try-catch

Validate before calling

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; }

Type guard

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; }

Try / catch

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; }

Prevention

When it happens

Trigger: 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.

Common situations: 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).

Related errors


AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12). Data as JSON: /api/errors/ddb75fac346d1c5b. Report an issue: GitHub.