paperclipai/paperclip · error · Error

Source path must stay inside raw/: ${path}

Error message

Source path must stay inside raw/: ${path}

What it means

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.

Source

Thrown at packages/plugins/plugin-llm-wiki/src/wiki/core.ts:1202

  if (normalized !== "index.md" && normalized !== "log.md" && normalized !== "WIKI.md" && normalized !== "AGENTS.md" && normalized !== "IDEA.md" && !normalized.startsWith("wiki/")) {
    throw new Error(`Wiki page writes must target AGENTS.md, IDEA.md, or wiki/: ${path}`);
  }
  if (!normalized.endsWith(".md")) {
    throw new Error(`Wiki page path must be a markdown file: ${path}`);
  }
  return normalized;
}

function assertPageWriteAllowed(path: string, writer: WritePageInput["writer"] = "agent_tool"): void {
  if (writer !== "board_ui" && PROTECTED_WIKI_CONTROL_FILES.has(path)) {
    throw new Error(`Refusing to overwrite protected wiki control file ${path}; board-managed edits must use the wiki UI.`);
  }
}

function assertRawPath(path: string): string {
  const normalized = assertWikiPath(path);
  if (!normalized.startsWith("raw/")) {
    throw new Error(`Source path must stay inside raw/: ${path}`);
  }
  return normalized;
}

function tableName(namespace: string, table: string): string {
  return `${namespace}.${table}`;
}

function spaceTable(ctx: PluginContext): string {
  return tableName(ctx.db.namespace, "wiki_spaces");
}

function bindingTable(ctx: PluginContext): string {
  return tableName(ctx.db.namespace, "wiki_resource_bindings");
}

function distillationCursorTable(ctx: PluginContext): string {
  return tableName(ctx.db.namespace, "paperclip_distillation_cursors");

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Prefix all raw asset paths with 'raw/' before calling the raw-source API.
  2. Use spaceRelativePath with a known raw root when constructing paths.
  3. Reject raw API requests at the boundary if the path does not start with 'raw/'.

Example fix

// before
await writeRawSource(ctx, { companyId, path: "diagrams/arch.png" });

// after
await writeRawSource(ctx, { companyId, path: "raw/diagrams/arch.png" });
Defensive patterns

Strategy: validation

Validate before calling

function ensureRawPrefix(path) {
  const t = String(path).trim().replace(/^\/+/, "");
  if (!t.startsWith("raw/")) throw new Error(`Source path must stay inside raw/: ${path}`);
  return t;
}

Type guard

function isRawPath(path) {
  return String(path ?? "").trim().replace(/^\/+/, "").startsWith("raw/");
}

Try / catch

try {
  await writeRawSource(ctx, { companyId, path, content });
} catch (err) {
  if (/must stay inside raw\//.test(err.message)) {
    return writeRawSource(ctx, { companyId, path: `raw/${String(path).replace(/^\/+/, "")}`, content });
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling a raw-source write/read API with a path like 'assets/foo.png', 'wiki/foo.png', or 'foo.png'.

Common situations: Forgetting the raw/ prefix when constructing an asset path; reusing a page path for a raw asset; UI upload form omitting the prefix.

Related errors


AI-assisted analysis of paperclipai/paperclip@67001ec6eb (2026-08-12). Data as JSON: /api/errors/80dba6545526bf65. Report an issue: GitHub.