nanocoai/nanoclaw · error

Template ref must be relative to the templates directory: "$

Error message

Template ref must be relative to the templates directory: "${ref}"

What it means

resolveLocalTemplate only accepts refs relative to the templates directory. This error fires when the ref is an absolute path ('/etc/templates/x') or starts with '~' — both would bypass the intended base directory, so they are rejected up front. This is both a safety boundary and a UX guard forcing template refs to be names relative to TEMPLATES_DIR.

Source

Thrown at src/templates/local-dir.ts:22

import { TEMPLATES_DIR } from '../config.js';

/**
 * Resolve a LOCAL template ref to an absolute directory under `base`
 * (TEMPLATES_DIR by default). Lexical containment only — no realpathSync, no
 * symlink resolution (out of threat model). Mirrors ensureWithinBase() in
 * group-folder.ts. Refs are legitimately multi-segment (e.g. "sales/sdr"), so
 * this does NOT reuse isValidGroupFolder (which rejects "/").
 *
 * Rejects: empty / untrimmed refs, absolute paths, a leading "~", and any ref
 * that escapes `base` after resolution. Throws if the resolved path is missing
 * or not a directory.
 */
export function resolveLocalTemplate(ref: string, base: string = TEMPLATES_DIR): string {
  if (!ref || ref !== ref.trim()) {
    throw new Error(`Invalid template ref: "${ref}"`);
  }
  if (path.isAbsolute(ref) || ref.startsWith('~')) {
    throw new Error(`Template ref must be relative to the templates directory: "${ref}"`);
  }
  const candidate = path.resolve(base, ref);
  const rel = path.relative(base, candidate);
  if (rel.startsWith('..') || path.isAbsolute(rel)) {
    throw new Error(`Template ref escapes the templates directory: "${ref}"`);
  }
  if (!fs.existsSync(candidate) || !fs.statSync(candidate).isDirectory()) {
    throw new Error(`Template not found: "${ref}" (looked in ${base})`);
  }
  return candidate;
}

View on GitHub (pinned to 294ef2aee8)

Solutions

  1. Pass only the template's relative name: resolveLocalTemplate('my-tpl').
  2. If you have an absolute path, either strip the TEMPLATES_DIR prefix or bypass the resolver and read the directory directly.
  3. Don't pre-expand '~' — pass the bare relative ref.

Example fix

// before
resolveLocalTemplate('/opt/nanoclaw/templates/basic-agent');

// after
resolveLocalTemplate('basic-agent');
Defensive patterns

Strategy: type-guard

Validate before calling

const isRelativeRef = (r: string): boolean => r.length > 0 && !path.isAbsolute(r) && !r.startsWith('~');
if (!isRelativeRef(ref)) throw new Error('template ref must be a name relative to the templates dir');

Type guard

const isRelativeTemplateRef = (r: unknown): r is string => typeof r === 'string' && r.length > 0 && !path.isAbsolute(r) && !r.startsWith('~');

Prevention

When it happens

Trigger: Calling resolveLocalTemplate('/home/me/templates/my-tpl') or '~/my-tpl'; building the ref by concatenating an absolute base path with a template name; users pasting a full path copied from a file explorer.

Common situations: Users pasting absolute paths into a --template flag; scripts that resolve a path first and then pass the resolved absolute path back into the resolver; '~' expansion done manually before calling.

Related errors


AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28). Data as JSON: /api/errors/876e3fd3a4e3337c. Report an issue: GitHub.