ruvnet/ruflo · error
File not found
Error message
File not found
What it means
Thrown by safeReadFile() (v3/@claude-flow/hooks/src/workers/index.ts:158) when the underlying fs.stat/fs.readFile fails with code ENOENT; the helper rewrites it into a plain 'File not found' Error. It means the path you asked for does not exist on disk at read time.
Solutions
- Treat 'File not found' on first run as expected: catch it and initialize with an empty/default state instead of failing the hook.
- Verify the path exists beforehand (fs.access) or ensure the parent directory is created with fs.mkdir(dir, { recursive: true }) before the first write.
- Log the exact absolute path in the catch to spot cwd/separator mistakes.
- If the file should exist, check for cleanup jobs, container volume mounts, or CI steps removing it.
Example fix
// before
const content = await safeReadFile(this.persistPath, 1024 * 1024);
// after
let content: string;
try {
content = await safeReadFile(this.persistPath, 1024 * 1024);
} catch (e) {
if ((e as Error).message !== 'File not found') throw e;
content = '{}'; // first run: no persisted state yet
} Defensive patterns
Strategy: validation
Validate before calling
try {
await fs.access(filePath);
} catch {
// first run or cleaned state: use defaults instead of reading
return defaultState;
} Type guard
async function fileExists(p: string): Promise<boolean> {
try { await fs.access(p); return true; } catch { return false; }
} Try / catch
try {
content = await safeReadFile(p);
} catch (e) {
if ((e as Error).message === 'File not found') return defaultState;
throw e;
} Prevention
- Create parent directories with mkdir(recursive: true) before first write.
- Treat missing state on first run as normal, not an error.
- Use absolute paths built from a single constant to avoid cwd-dependent resolution.
When it happens
Trigger: Reading a worker persistence file on first run before anything was written (persistPath never created); a race where the file is deleted between listing a directory and reading an entry; a typo'd or wrongly-resolved path (relative path resolved against an unexpected cwd).
Common situations: First-ever invocation of a session hook with no prior state; state directory cleaned by a cache clearer or CI step; path built from a config value with a wrong separator; file on a mounted volume that is not attached yet.
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 budget file is a symlink (refusing)
- AI job registry is a symlink (refusing)
- build evidence path is not a file or symlink
- Cannot write to project path
- case-fold repository path collision
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/e64eec3b0e61d232.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/hooks/src/workers/index.ts:158
}
}
return content;
}
/**
* Safe file read with size limit
*/
async function safeReadFile(filePath: string, maxSize = MAX_FILE_SIZE): Promise<string> {
try {
const stats = await fs.stat(filePath);
if (stats.size > maxSize) {
throw new Error(`File too large: ${stats.size} > ${maxSize}`);
}
return await fs.readFile(filePath, 'utf-8');
} catch (error) {
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
throw new Error('File not found');
}
throw error;
}
}
/**
* Validate project root is a real directory
*/
async function validateProjectRoot(root: string): Promise<string> {
const resolved = path.resolve(root);
try {
const stats = await fs.stat(resolved);
if (!stats.isDirectory()) {
throw new Error('Project root must be a directory');
}
return resolved;
} catch {
// If we can't validate, use cwd as fallbackView on GitHub (pinned to fa13ee4ad6)