ruvnet/ruflo · error · Error
File not found
Error message
File not found
What it means
In the rvf (file-backed/in-memory) storage backend, openDownloadStream(id).toArray() looks up the base64 blob in an in-memory files Map (rvf.ts:1056) and throws "File not found" on a miss. The Map is hydrated from the on-disk store (./db JSON persistence) at startup, so a miss means the id was never persisted in this store or the persisted data is gone.
Solutions
- Confirm which store the file was written to (check the db JSON file's files keys) and read from the same backend
- Return a friendly 404 from the download path when the blob is gone, and clean the dangling message reference
- Pin the store location with an absolute DATA_DIR so restarts from any cwd see the same data
- Re-upload or restore the store file from backup
Example fix
// before
const [buf] = await collections.bucket.openDownloadStream(fileId).toArray(); // throws: File not found
// after
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(); 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"); 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") throw error(404, "File expired");
throw e;
} Prevention
- Pin the rvf store to an absolute path (stable cwd/DATA_DIR) so restarts see the same files
- Treat upload+persist as atomic — call scheduleSave immediately after writes
- Don't mix backends across restarts (MONGODB_URL set vs unset) for the same data set
When it happens
Trigger: Awaiting bucket.openDownloadStream(fileId).toArray() when the rvf store's files Map has no entry for the id: server restarted with a missing/empty store file, DATA_DIR/working directory changed between runs (scheduleSave writes relative paths), conversations imported from another backend, or the file was deleted via bucket.delete().
Common situations: Dev server run from a different cwd so ./db resolves elsewhere; wiping the db folder but keeping conversation records; switching MONGODB_URL on/off so uploads landed in one backend and reads hit another; concurrent saves losing a just-uploaded file after a crash.
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/7e2b2c9f0e546468.
Report an issue: GitHub.
Appendix: source
Thrown at ruflo/src/ruvocal/src/lib/server/database/rvf.ts:1056
filename,
contentType: options?.contentType ?? "application/octet-stream",
length: data.length,
data,
metadata: options?.metadata ?? {},
createdAt: new Date(),
});
scheduleSave();
},
};
}
openDownloadStream(id: ObjectId | string) {
const fileId = typeof id === "string" ? id : id.toString();
const files = this.files;
return {
async toArray(): Promise<Buffer[]> {
const file = files.get(fileId);
if (!file) throw new Error("File not found");
return [Buffer.from(file.data as string, "base64")];
},
};
}
async delete(id: ObjectId | string) {
const fileId = typeof id === "string" ? id : id.toString();
this.files.delete(fileId);
scheduleSave();
}
async find(filter: Record<string, unknown> = {}) {
const results: Record<string, unknown>[] = [];
for (const doc of this.files.values()) {
if (matchesFilter(doc, filter)) {
const { data, ...meta } = doc;
results.push(meta);
}View on GitHub (pinned to fa13ee4ad6)