{"record":{"id":"e5b97739e5d8e60a","repo":"can1357/oh-my-pi","slug":"this-write-tool-is-limited-to-the-xd-device-t","errorCode":null,"errorMessage":"This `write` tool is limited to the xd:// device transport: call it with path `xd://<tool>` and the device's JSON arguments in `content` (`read xd://` lists mounted devices). Active plan mode additionally permits its local artifact sandbox.","messagePattern":"This `write` tool is limited to the xd:// device transport: call it with path `xd://<tool>` and the device's JSON arguments in `content` \\(`read xd://` lists mounted devices\\)\\. Active plan mode additionally permits its local artifact sandbox\\.","errorType":"validation","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/write.ts","lineNumber":1132,"sourceCode":"\t\t// filesystem target. Without this, a model that pastes a `read`\n\t\t// header as the `path` arg would slip past `isInternalUrlPath`\n\t\t// (which fails on a leading `[`) and the bridge router would send a\n\t\t// `[local://scratch.md#ABCD]` write to the editor instead of the\n\t\t// session-local sandbox.\n\t\t// Peel a read-tool selector (`:raw`, `:1-20`, …) so the write target matches\n\t\t// what `read` resolves for the same URL; line-range/malformed selectors throw.\n\t\tconst path = peelWriteUrlSelector(unwrapHashlineHeaderPath(rawPath));\n\t\t// A device-only session grants `write` purely as the xd:// transport (see\n\t\t// createTools): device dispatches proceed, every other target is rejected\n\t\t// before any handler, guard, conflict resolver, or bridge sees it. Active\n\t\t// plan mode additionally permits its local artifact sandbox, but does not\n\t\t// relax the restriction for working-tree or non-xd internal URLs.\n\t\tif (\n\t\t\tthis.session.deviceOnlyWrite === true &&\n\t\t\t!parseXdUrl(path) &&\n\t\t\t!(this.session.getPlanModeState?.()?.enabled === true && targetsLocalSandbox(this.session, path))\n\t\t) {\n\t\t\tthrow new ToolError(\n\t\t\t\t\"This `write` tool is limited to the xd:// device transport: call it with path `xd://<tool>` and the device's JSON arguments in `content` (`read xd://` lists mounted devices). Active plan mode additionally permits local:// sandbox drafts. Filesystem writes are not available elsewhere.\",\n\t\t\t);\n\t\t}\n\t\treturn untilAborted(signal, async () => {\n\t\t\t// Strip hashline display prefixes ([PATH#HASH] + LINE:) if the model copied them from read output\n\t\t\tconst { text: cleanContent, stripped } = stripWriteContent(this.session, content);\n\t\t\tconst internalRouter = InternalUrlRouter.instance();\n\t\t\tassertWriteTargetAddressable(path, internalRouter);\n\t\t\tif (internalRouter.canHandle(path)) {\n\t\t\t\tconst parsed = parseInternalUrl(path);\n\t\t\t\tconst scheme = parsed.protocol.replace(/:$/, \"\").toLowerCase();\n\t\t\t\tconst handler = internalRouter.getHandler(scheme);\n\t\t\t\tif (handler?.write) {\n\t\t\t\t\t// Handler-owned writes mutate user data outside the local\n\t\t\t\t\t// sandbox. xd:// dispatches retain each wrapped tool's tier.\n\t\t\t\t\tif (scheme !== \"xd\") {\n\t\t\t\t\t\tenforcePlanModeWrite(this.session, path, { op: \"update\" });\n\t\t\t\t\t\temitWriteProgress(onUpdate, cleanContent, path);","sourceCodeStart":1114,"sourceCodeEnd":1150,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/write.ts#L1114-L1150","documentation":"When the session is configured with deviceOnlyWrite, the write tool is restricted to the xd:// device transport (and, while plan mode is active, the local:// artifact sandbox). Any other path is rejected up front with this ToolError explaining the transport restriction and how to list mounted devices via `read xd://`.","triggerScenarios":"Calling write with a normal filesystem path (or non-xd internal URL) while session.deviceOnlyWrite === true and plan mode is not active (or the path is not inside the local sandbox).","commonSituations":"Agent working in a device-only session attempting to write workspace files; plan-mode drafts pointing outside the sanctioned sandbox; sessions launched with a restricted/device flag where the model still tries regular file edits.","solutions":["Rewrite the call to target `xd://<tool>` with the device's JSON arguments in content.","Run `read xd://` to list mounted devices and their accepted arguments.","If a local draft is appropriate, enable/use plan mode and write inside the local:// sandbox.","If filesystem writes are genuinely needed, restart the session without deviceOnlyWrite."],"exampleFix":"// before\nawait write(\"src/config.ts\", \"...\")\n// after\nawait write(\"xd://editor\", \"{ file: 'src/config.ts', content: '...' }\")","handlingStrategy":"validation","validationCode":"const isXd = path.startsWith(\"xd://\");\nconst isPlanSandbox = planModeEnabled && path.startsWith(\"local://\");\nif (deviceOnlyWrite && !isXd && !isPlanSandbox) {\n  throw new Error(\"Write must target xd:// (or local:// sandbox in plan mode)\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  await write(path, content);\n} catch (err) {\n  if (String(err.message).includes(\"limited to the xd:// device transport\")) {\n    // re-dispatch as xd://<tool> with JSON content\n  }\n  throw err;\n}","preventionTips":["In device-only sessions, route all writes through xd:// targets.","Call `read xd://` first to learn mounted devices and their argument schemas.","Use the local:// sandbox for drafts only while plan mode is active."],"tags":["permissions","device-transport","policy"],"backgroundTag":"operation-not-permitted","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}