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

  1. Point the path at an actual regular file, not a directory or socket.
  2. Verify with fs.lstat(...).isFile() before calling.
  3. Recreate the expected config file if something replaced it with a FIFO/socket.
  4. 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

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


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/f370f5d55f715914. Report an issue: GitHub.