different-ai/openwork · error · Error
NOT_REGULAR_FILE
NOT_REGULAR_FILE
Error message
Expected a regular file
What it means
readBoundedRegularTextFile refuses to read paths that lstat reports as anything other than a regular file, throwing an Error with code NOT_REGULAR_FILE. This blocks reading directories, FIFOs, sockets, and device nodes, preventing indefinite hangs on FIFOs and accidental device reads when loading small diagnostics/config files.
Source
Thrown at apps/server/src/jsonc.ts:60
}
/**
* Read a small diagnostics input without following symlinks or opening a FIFO
* in blocking mode. The size is checked both before and while reading so a
* file that grows after inspection cannot exceed the caller's memory budget.
*/
export async function readBoundedRegularTextFile(
path: string,
options: { maxBytes: number; signal?: AbortSignal },
): Promise<string> {
if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 0) {
throw new RangeError("maxBytes must be a non-negative safe integer");
}
throwIfAborted(options.signal);
const pathMetadata = await lstat(path);
throwIfAborted(options.signal);
if (!pathMetadata.isFile()) {
throw fileReadError("NOT_REGULAR_FILE", "Expected a regular file");
}
if (pathMetadata.size > options.maxBytes) {
throw fileReadError("FILE_TOO_LARGE", "File exceeds the configured read limit");
}
const nonBlockingFlags = process.platform === "win32"
? 0
: constants.O_NONBLOCK | constants.O_NOFOLLOW;
const handle = await open(path, constants.O_RDONLY | nonBlockingFlags);
try {
const openedMetadata = await handle.stat();
throwIfAborted(options.signal);
if (!openedMetadata.isFile()) {
throw fileReadError("NOT_REGULAR_FILE", "Expected a regular file");
}
if (openedMetadata.size > options.maxBytes) {
throw fileReadError("FILE_TOO_LARGE", "File exceeds the configured read limit");
}View on GitHub (pinned to 2b7df46e8a)
Solutions
- Point the path at an actual regular file, not a directory or socket.
- Verify with fs.lstat(...).isFile() before calling.
- Recreate the expected config file if something replaced it with a FIFO/socket.
- If a symlink to a regular file is intended, copy the target to a regular file path instead.
Example fix
// before
const cfg = await readJsoncFile("/etc/openwork"); // directory -> NOT_REGULAR_FILE
// after
const st = await fs.lstat("/etc/openwork/openwork.jsonc");
if (!st.isFile()) throw new Error("config path must be a regular file");
const cfg = await readJsoncFile("/etc/openwork/openwork.jsonc"); Defensive patterns
Strategy: validation
Validate before calling
const st = await fs.lstat(path);
if (!st.isFile()) throw new Error(`${path} is not a regular file`); Type guard
null
Try / catch
try {
return await readJsoncFile(path);
} catch (e) {
if ((e as NodeJS.ErrnoException).code === "NOT_REGULAR_FILE") return defaultConfig;
throw e;
} Prevention
- Always lstat and isFile()-check user-supplied paths first.
- Reject directories/sockets/FIFOs at config-load time with a clear message.
- Watch for volume mounts that replace config file paths with directories.
- Regenerate missing config files instead of pointing at placeholders.
When it happens
Trigger: Calling readJsoncFile or raw on a path that lstat shows is a directory, named pipe (FIFO), socket, device node, or similar non-regular file.
Common situations: Pointing a config path at a directory (e.g. ~/.config/openwork instead of a file inside it); a service creating a FIFO/socket where a JSONC file was expected; a Docker volume mounting a directory over a config file path.
Related errors
- FILE_TOO_LARGE
- ${result}
- Workspace path is unavailable; attachments could not be copi
- Failed to write .opencode/openwork.json
- plugin_manifest_not_found
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/f370f5d55f715914.
Report an issue: GitHub.