nanocoai/nanoclaw · error
Invalid template ref: "${ref}"
Error message
Invalid template ref: "${ref}" What it means
resolveLocalTemplate validates the user-supplied template ref before resolving it against the templates directory. This variant throws when the ref is empty or has leading/trailing whitespace — refs must be exact, non-empty relative names. (A separate error covers absolute paths and '~', and another covers escaping.)
Source
Thrown at src/templates/local-dir.ts:19
import fs from 'fs';
import path from 'path';
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
- Trim before calling: resolveLocalTemplate(ref.trim()).
- Validate the ref is non-empty after trimming and surface a friendly CLI error first.
- Sanitize template lists in config so they contain no blank entries.
Example fix
// before resolveLocalTemplate(rawArg); // after resolveLocalTemplate(rawArg?.trim() ?? '').
Defensive patterns
Strategy: validation
Validate before calling
const ref = rawRef?.trim() ?? '';
if (!ref) throw new Error('template ref required');
if (!/^[a-z0-9][a-z0-9._/-]*$/i.test(ref)) throw new Error(`bad template ref: "${ref}"`); Type guard
const isValidTemplateRef = (r: unknown): r is string => typeof r === 'string' && r.length > 0 && r === r.trim() && !r.startsWith('/') && !r.startsWith('~'); Prevention
- Trim CLI/config inputs once at the boundary.
- Filter empty entries out of template lists.
When it happens
Trigger: Calling with ref = '', ' basic-agent ', or a value built from untrimmed user input (CLI arg, config field) that carries whitespace; also undefined/null coerced to a string.
Common situations: Passing a template name straight from a config file or CLI argument without trimming; copy-pasting a template name with a trailing newline from a list; template lists containing an empty entry.
Related errors
- ${settingsFile} hooks.SessionStart must be an array
- Unknown provider: ${name}. Registered: ${known}
- Channel '${channelKey}' declares engageMode 'pattern' withou
- engage_mode 'pattern' requires --engage-pattern (use "." to
- engage_mode '${w.engage_mode}' can never engage on channel '
AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28).
Data as JSON: /api/errors/adc5b752ea372683.
Report an issue: GitHub.