paperclipai/paperclip · error · Error

template path must be one of ${TEMPLATE_FILES.join(", ")}

Error message

template path must be one of ${TEMPLATE_FILES.join(", ")}

What it means

Thrown by readTemplate when input.path is not in TEMPLATE_FILES (['AGENTS.md','IDEA.md']). Templates are an enumerated set, not arbitrary files; the guard prevents reading non-template files from the WIKI_ROOT_FOLDER. It runs before the filesystem read.

Source

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

  );
  const row = meta[0] ?? null;
  return {
    wikiId,
    spaceSlug: space.slug,
    path,
    contents,
    title: row?.title ?? inferTitle(path, contents),
    pageType: row?.page_type ?? inferPageType(path),
    backlinks: Array.isArray(row?.backlinks) ? row?.backlinks : [],
    sourceRefs: Array.isArray(row?.source_refs) ? row?.source_refs : [],
    updatedAt: row?.updated_at ?? null,
    hash: contentHash(contents),
  };
}

export async function readTemplate(ctx: PluginContext, input: { companyId: string; path: string }) {
  if (!isTemplateFile(input.path)) {
    throw new Error(`template path must be one of ${TEMPLATE_FILES.join(", ")}`);
  }
  try {
    const contents = await ctx.localFolders.readText(input.companyId, WIKI_ROOT_FOLDER_KEY, input.path);
    return { path: input.path, contents, hash: contentHash(contents), exists: true };
  } catch (error) {
    return { path: input.path, contents: "", hash: null, exists: false, error: error instanceof Error ? error.message : String(error) };
  }
}

export async function writeTemplate(ctx: PluginContext, input: { companyId: string; path: string; contents: string }) {
  if (!isTemplateFile(input.path)) {
    throw new Error(`template path must be one of ${TEMPLATE_FILES.join(", ")}`);
  }
  await ctx.localFolders.writeTextAtomic(input.companyId, WIKI_ROOT_FOLDER_KEY, input.path, input.contents);
  return { status: "ok", path: input.path, hash: contentHash(input.contents) };
}

export type DistillationCursorRow = {

View on GitHub (pinned to 67001ec6eb)

Solutions

  1. Use exactly 'AGENTS.md' or 'IDEA.md' (case-sensitive).
  2. If you need to read a non-template wiki file, use readWikiPage instead.
  3. Validate the path against the TEMPLATE_FILES list at the caller before invoking readTemplate.

Example fix

// before
readTemplate(ctx, { companyId, path: 'agents.md' });
// after
readTemplate(ctx, { companyId, path: 'AGENTS.md' });
Defensive patterns

Strategy: type-guard

Validate before calling

const TEMPLATE_FILES = ['AGENTS.md', 'IDEA.md'] as const;
function assertTemplatePath(path: string) {
  if (!TEMPLATE_FILES.includes(path as any)) throw new Error(`path must be one of ${TEMPLATE_FILES.join(', ')}`);
}

Type guard

const TEMPLATE_FILES = ['AGENTS.md', 'IDEA.md'] as const;
type WikiTemplateFile = typeof TEMPLATE_FILES[number];
function isTemplateFile(path: string): path is WikiTemplateFile {
  return (TEMPLATE_FILES as readonly string[]).includes(path);
}

Prevention

When it happens

Trigger: Calling readTemplate with a path other than 'AGENTS.md' or 'IDEA.md' (e.g. 'agents.md' lowercase, 'README.md', a directory path).

Common situations: Case mismatch (the check is case-sensitive via isTemplateFile); copying a path from a wiki page that is not a template; passing a user-supplied path without validation.

Related errors


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