{"record":{"id":"80dba6545526bf65","repo":"paperclipai/paperclip","slug":"source-path-must-stay-inside-raw-path","errorCode":null,"errorMessage":"Source path must stay inside raw/: ${path}","messagePattern":"Source path must stay inside raw/: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugins/plugin-llm-wiki/src/wiki/core.ts","lineNumber":1202,"sourceCode":"  if (normalized !== \"index.md\" && normalized !== \"log.md\" && normalized !== \"WIKI.md\" && normalized !== \"AGENTS.md\" && normalized !== \"IDEA.md\" && !normalized.startsWith(\"wiki/\")) {\n    throw new Error(`Wiki page writes must target AGENTS.md, IDEA.md, or wiki/: ${path}`);\n  }\n  if (!normalized.endsWith(\".md\")) {\n    throw new Error(`Wiki page path must be a markdown file: ${path}`);\n  }\n  return normalized;\n}\n\nfunction assertPageWriteAllowed(path: string, writer: WritePageInput[\"writer\"] = \"agent_tool\"): void {\n  if (writer !== \"board_ui\" && PROTECTED_WIKI_CONTROL_FILES.has(path)) {\n    throw new Error(`Refusing to overwrite protected wiki control file ${path}; board-managed edits must use the wiki UI.`);\n  }\n}\n\nfunction assertRawPath(path: string): string {\n  const normalized = assertWikiPath(path);\n  if (!normalized.startsWith(\"raw/\")) {\n    throw new Error(`Source path must stay inside raw/: ${path}`);\n  }\n  return normalized;\n}\n\nfunction tableName(namespace: string, table: string): string {\n  return `${namespace}.${table}`;\n}\n\nfunction spaceTable(ctx: PluginContext): string {\n  return tableName(ctx.db.namespace, \"wiki_spaces\");\n}\n\nfunction bindingTable(ctx: PluginContext): string {\n  return tableName(ctx.db.namespace, \"wiki_resource_bindings\");\n}\n\nfunction distillationCursorTable(ctx: PluginContext): string {\n  return tableName(ctx.db.namespace, \"paperclip_distillation_cursors\");","sourceCodeStart":1184,"sourceCodeEnd":1220,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/plugins/plugin-llm-wiki/src/wiki/core.ts#L1184-L1220","documentation":"Thrown by assertRawPath when assertWikiPath succeeds but the normalized path does not start with 'raw/'. All raw source assets must live under that prefix so the wiki can separate them from page content.","triggerScenarios":"Calling a raw-source write/read API with a path like 'assets/foo.png', 'wiki/foo.png', or 'foo.png'.","commonSituations":"Forgetting the raw/ prefix when constructing an asset path; reusing a page path for a raw asset; UI upload form omitting the prefix.","solutions":["Prefix all raw asset paths with 'raw/' before calling the raw-source API.","Use spaceRelativePath with a known raw root when constructing paths.","Reject raw API requests at the boundary if the path does not start with 'raw/'."],"exampleFix":"// before\nawait writeRawSource(ctx, { companyId, path: \"diagrams/arch.png\" });\n\n// after\nawait writeRawSource(ctx, { companyId, path: \"raw/diagrams/arch.png\" });","handlingStrategy":"validation","validationCode":"function ensureRawPrefix(path) {\n  const t = String(path).trim().replace(/^\\/+/, \"\");\n  if (!t.startsWith(\"raw/\")) throw new Error(`Source path must stay inside raw/: ${path}`);\n  return t;\n}","typeGuard":"function isRawPath(path) {\n  return String(path ?? \"\").trim().replace(/^\\/+/, \"\").startsWith(\"raw/\");\n}","tryCatchPattern":"try {\n  await writeRawSource(ctx, { companyId, path, content });\n} catch (err) {\n  if (/must stay inside raw\\//.test(err.message)) {\n    return writeRawSource(ctx, { companyId, path: `raw/${String(path).replace(/^\\/+/, \"\")}`, content });\n  }\n  throw err;\n}","preventionTips":["Always prefix raw asset paths with raw/.","Use spaceRelativePath with a known raw root.","Reject raw API requests missing the prefix."],"tags":["validation","wiki","paths","paperclip"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}