{"record":{"id":"c60580649c55b5d2","repo":"can1357/oh-my-pi","slug":"dynamic-write-policy-refusal-message-throw-new-e","errorCode":null,"errorMessage":"(dynamic write-policy refusal message: throw new Error(refusal))","messagePattern":"\\(dynamic write-policy refusal message: throw new Error\\(refusal\\)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/cursor.ts","lineNumber":796,"sourceCode":"\t * native `delete` frame — on the session actually granting a file-writing\n\t * tool, and on the user's `write`-tier policy. The gate runs before the read\n\t * so a refused download never fetches the resource either.\n\t */\n\tasync readMcpResource({\n\t\tserver,\n\t\turi,\n\t\tdownloadPath,\n\t}: {\n\t\tserver: string;\n\t\turi: string;\n\t\tdownloadPath?: string;\n\t}): Promise<CursorMcpResourceContent | null> {\n\t\tif (downloadPath) {\n\t\t\tif (!allowsDirectFileMutation(this.options)) {\n\t\t\t\tthrow new Error('Tool \"write\" not available: this session cannot download resources to disk.');\n\t\t\t}\n\t\t\tconst refusal = refuseByWritePolicy(this.options, \"write\", downloadPath);\n\t\t\tif (refusal) throw new Error(refusal);\n\t\t}\n\t\tconst mcp = this.options.mcpResources;\n\t\tif (!mcp) return null;\n\t\tconst read = await mcp.readServerResource(server, uri);\n\t\tif (!read) return null;\n\t\t// The mime type must describe the bytes actually sent, not whatever item\n\t\t// happened to be first: an image blob followed by a text note would\n\t\t// otherwise label the text `image/png` and mislead the model about what\n\t\t// it is holding. Each branch below takes the type from its own producer.\n\t\tconst textItems = read.contents.filter(item => item.text !== undefined);\n\t\tconst texts = textItems.map(item => item.text as string);\n\t\tconst blobItem = read.contents.find(item => item.blob !== undefined);\n\t\tconst blob = blobItem?.blob;\n\t\tconst textMimeType = textItems[0]?.mimeType;\n\t\tconst blobMimeType = blobItem?.mimeType;\n\n\t\tif (downloadPath) {\n\t\t\t// Text resources download as their own bytes; a blob decodes first.","sourceCodeStart":778,"sourceCodeEnd":814,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/cursor.ts#L778-L814","documentation":"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.","triggerScenarios":"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.","commonSituations":"Downloading to a path outside the workspace allowlist; a deny rule for config or credential files; organization policy restricting writes to certain directories.","solutions":["Read the refusal message — it names the path and rule; move the downloadPath inside the permitted area (typically the workspace).","Update the write policy / permission rules (allow the directory or file glob) if the download is intentional.","Approve the write when prompted by the permission system, or pre-authorize the path in settings.","Download to the project directory instead of an absolute outside path."],"exampleFix":"// before: outside allowed write roots\n{ downloadPath: \"/etc/app/config.yaml\" }\n// after: inside the workspace\n{ downloadPath: \"downloads/config.yaml\" }","handlingStrategy":"validation","validationCode":"const refusal = refuseByWritePolicy(options, \"write\", downloadPath);\nif (refusal) {\n  // don't attempt the download; show the refusal to the user\n  console.warn(refusal);\n} else {\n  await readMcpResource({ server, uri, downloadPath });\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await readMcpResource({ server, uri, downloadPath });\n} catch (e) {\n  if (/not (allowed|permitted)|write policy|outside/i.test(String(e.message))) {\n    // retry with a path inside the permitted write roots\n    return readMcpResource({ server, uri, downloadPath: withinWorkspace(downloadPath) });\n  } else throw e;\n}","preventionTips":["Pre-authorize download directories in the write policy before requesting downloads.","Keep downloadPath inside directories the policy allows (usually the workspace).","Read and honor the dynamic refusal message — it names the offending path/rule."],"tags":["permissions","security","mcp","write-policy"],"backgroundTag":"write-permission-denied","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}