can1357/oh-my-pi · error · Error

Refusing to download onto a non-regular file: ${absolutePath

Error message

Refusing to download onto a non-regular file: ${absolutePath}

What it means

After opening the destination without following symlinks, the writer stats the handle and refuses if the inode is not a regular file. Downloads may only land on plain files, never directories, devices, sockets, or FIFOs that slipped past the open flags. This is an explicit integrity guard around overwriting existing content.

Source

Thrown at packages/coding-agent/src/cursor.ts:193

async function writeWithoutFollowingLinks(absolutePath: string, payload: string | Buffer): Promise<void> {
	await fs.promises.mkdir(path.dirname(absolutePath), { recursive: true });
	const handle = await fs.promises
		.open(
			absolutePath,
			fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK,
		)
		.catch((error: NodeJS.ErrnoException) => {
			// A readerless FIFO. Reported as the refusal it is, rather than the
			// bare "no such device or address" the errno spells out.
			if (error.code === "ENXIO") {
				throw new Error(`Refusing to download onto a special file: ${absolutePath}`);
			}
			throw error;
		});
	try {
		const stat = await handle.stat();
		if (!stat.isFile()) {
			throw new Error(`Refusing to download onto a non-regular file: ${absolutePath}`);
		}
		if (stat.nlink > 1) {
			throw new Error(
				`Refusing to download onto a file with ${stat.nlink} hard links, which would overwrite its other names: ${absolutePath}`,
			);
		}
		await handle.truncate(0);
		await handle.writeFile(payload);
	} finally {
		await handle.close();
	}
}

function createToolResultMessage(
	toolCallId: string,
	toolName: string,
	result: AgentToolResult<unknown>,
	isError: boolean,

View on GitHub (pinned to 9690622007)

Solutions

  1. Choose a download path that is a regular file (or does not exist yet).
  2. If a directory occupies the name, remove or rename the directory first (`rm -r <path>`).
  3. Do not target device or socket nodes; pick a path under your workspace.

Example fix

// before
{ downloadPath: "/tmp/out" }  // /tmp/out is a directory
// after
{ downloadPath: "/tmp/out/report.json" }
Defensive patterns

Strategy: validation

Validate before calling

const s = await stat(downloadPath).catch(() => null);
if (s && !s.isFile()) throw new Error(`${downloadPath} exists and is not a regular file`);

Type guard

function isPlainFile(s: import("node:fs").Stats | undefined): boolean {
  return !!s && s.isFile();
}

Try / catch

try {
  await downloadResource(res, downloadPath);
} catch (e) {
  if (String(e.message).startsWith("Refusing to download onto a non-regular file")) {
    // choose a different destination path
  } else throw e;
}

Prevention

When it happens

Trigger: The downloadPath resolves to an existing directory, socket, block/character device, or another special inode (a case the ENXIO open guard doesn't cover, e.g. a FIFO with a reader or a directory opened with O_CREAT which fails differently).

Common situations: A resource name collides with a directory of the same name; pointing the download at /dev/null or a unix socket; a stale symlink-target directory.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/32b491a41c50c809. Report an issue: GitHub.