can1357/oh-my-pi · error · Error

(dynamic write-policy refusal message: throw new Error(refus

Error message

(dynamic write-policy refusal message: throw new Error(refusal))

What it means

When direct file mutation is allowed, the download path is still checked against the session's write policy (permissions/allowlist rules) via refuseByWritePolicy. If the policy disallows writing to that specific path, its human-readable refusal message is thrown verbatim. The message is dynamic — it names the path and the violated rule.

Source

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

	 * native `delete` frame — on the session actually granting a file-writing
	 * tool, and on the user's `write`-tier policy. The gate runs before the read
	 * so a refused download never fetches the resource either.
	 */
	async readMcpResource({
		server,
		uri,
		downloadPath,
	}: {
		server: string;
		uri: string;
		downloadPath?: string;
	}): Promise<CursorMcpResourceContent | null> {
		if (downloadPath) {
			if (!allowsDirectFileMutation(this.options)) {
				throw new Error('Tool "write" not available: this session cannot download resources to disk.');
			}
			const refusal = refuseByWritePolicy(this.options, "write", downloadPath);
			if (refusal) throw new Error(refusal);
		}
		const mcp = this.options.mcpResources;
		if (!mcp) return null;
		const read = await mcp.readServerResource(server, uri);
		if (!read) return null;
		// The mime type must describe the bytes actually sent, not whatever item
		// happened to be first: an image blob followed by a text note would
		// otherwise label the text `image/png` and mislead the model about what
		// it is holding. Each branch below takes the type from its own producer.
		const textItems = read.contents.filter(item => item.text !== undefined);
		const texts = textItems.map(item => item.text as string);
		const blobItem = read.contents.find(item => item.blob !== undefined);
		const blob = blobItem?.blob;
		const textMimeType = textItems[0]?.mimeType;
		const blobMimeType = blobItem?.mimeType;

		if (downloadPath) {
			// Text resources download as their own bytes; a blob decodes first.

View on GitHub (pinned to 9690622007)

Solutions

  1. Read the refusal message — it names the path and rule; move the downloadPath inside the permitted area (typically the workspace).
  2. Update the write policy / permission rules (allow the directory or file glob) if the download is intentional.
  3. Approve the write when prompted by the permission system, or pre-authorize the path in settings.
  4. Download to the project directory instead of an absolute outside path.

Example fix

// before: outside allowed write roots
{ downloadPath: "/etc/app/config.yaml" }
// after: inside the workspace
{ downloadPath: "downloads/config.yaml" }
Defensive patterns

Strategy: validation

Validate before calling

const refusal = refuseByWritePolicy(options, "write", downloadPath);
if (refusal) {
  // don't attempt the download; show the refusal to the user
  console.warn(refusal);
} else {
  await readMcpResource({ server, uri, downloadPath });
}

Try / catch

try {
  return await readMcpResource({ server, uri, downloadPath });
} catch (e) {
  if (/not (allowed|permitted)|write policy|outside/i.test(String(e.message))) {
    // retry with a path inside the permitted write roots
    return readMcpResource({ server, uri, downloadPath: withinWorkspace(downloadPath) });
  } else throw e;
}

Prevention

When it happens

Trigger: readMcpResource with a downloadPath that the write policy rejects: outside approved directories, matching a deny glob, or blocked by user-configured permission rules when the write tool itself is enabled.

Common situations: Downloading to a path outside the workspace allowlist; a deny rule for config or credential files; organization policy restricting writes to certain directories.

Related errors


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