ruvnet/ruflo · error · Error

File not found

Error message

File not found

What it means

In the PostgreSQL backend the GridFS bucket is emulated: openDownloadStream(id).toArray() runs SELECT data FROM files WHERE _id = $1 and throws "File not found" when zero rows match (postgres.ts:668). This is the Postgres equivalent of Mongo GridFS failing to open a stream for an unknown file id, used by file downloads (files/downloadFile.ts) and conversation attachment loading (conversation.ts:63).

Solutions

  1. Verify the id exists first: const found = await collections.bucket.find({ _id: id }).toArray() and 404 gracefully when empty
  2. If ids mismatch, check whether the stored _id is the ObjectId string and you are passing an ObjectId (or vice versa) — both are accepted via toString(), so confirm the raw value
  3. If the row was deleted legitimately, remove the dangling reference from the message/file metadata or re-upload the asset
  4. Restore the files table from backup if the wipe was accidental

Example fix

// before
const [buf] = await collections.bucket.openDownloadStream(fileId).toArray(); // throws: File not found

// after
let buf: Buffer;
try {
	[buf] = await collections.bucket.openDownloadStream(fileId).toArray();
} catch (e) {
	if (e instanceof Error && e.message === "File not found") {
		throw error(404, "Attachment no longer available");
	}
	throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

const existing = await collections.bucket.find({ _id: fileId }).toArray();
if (existing.length === 0) throw error(404, "File no longer available");
const [buf] = await collections.bucket.openDownloadStream(fileId).toArray();

Type guard

null

Try / catch

try {
	const [buf] = await collections.bucket.openDownloadStream(fileId).toArray();
} catch (e) {
	if (e instanceof Error && e.message === "File not found") {
		// degrade gracefully: 404 / placeholder instead of a 500
		throw error(404, "Attachment no longer available");
	}
	throw e;
}

Prevention

When it happens

Trigger: Awaiting collections.bucket.openDownloadStream(fileId).toArray() with an id that has no row in the files table: file was deleted by cleanup/TTL, conversation still references a file id from a previous database (or from the Mongo/rvf backend before switching to Postgres), id string was mutated (ObjectId vs toString mismatch), or the table was wiped.

Common situations: Migrating storage backends while keeping old conversations; resetting the Postgres DB in dev but keeping seeded conversations; a moderation/gdpr job deleting file rows but not message metadata; tests calling toArray() with random ids.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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

Appendix: 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 fa13ee4ad6)