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
- Pass only the template's relative name: resolveLocalTemplate('my-tpl').
- If you have an absolute path, either strip the TEMPLATES_DIR prefix or bypass the resolver and read the directory directly.
- 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
- Pass template names, never paths, through user-facing flags.
- Document that refs resolve against TEMPLATES_DIR only.
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
- ${settingsFile} hooks.SessionStart must be an array
- engage_mode '${w.engage_mode}' can never engage on channel '
- chat-sdk bridge instance ${JSON.stringify(config.instance)}
- no verb "${verbArg}" on ${res.plural} — run `ncl ${res.plura
- --${column.name.replace(/_/g, '-')} must be a number
AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28).
Data as JSON: /api/errors/876e3fd3a4e3337c.
Report an issue: GitHub.