nanocoai/nanoclaw · error
no verb "${verbArg}" on ${res.plural} — run `ncl ${res.plura
Error message
no verb "${verbArg}" on ${res.plural} — run `ncl ${res.plural} help` What it means
An entry deeper than MAX_PLUGIN_DEPTH levels of nesting throws with its relative path. The depth cap bounds recursion on hostile or pathological trees before any content is read.
Source
Thrown at src/cli/commands/help.ts:96
description: `Show ${res.name} resource details; \`${res.plural} help <verb>\` for one verb in depth.`,
access: 'open',
resource: res.plural,
parseArgs: (raw) => raw,
handler: async (args, ctx) => {
const cliScope = await getCliScope(ctx);
const lines: string[] = [];
// `ncl <resource> help <verb>` arrives via the dispatcher's
// longest-prefix fallback (`groups-help-create` → `groups-help` +
// id=`create`) and renders that verb's deep help — full description,
// flags, examples. No new routing. Group-scope auto-fill also puts
// the caller's agent group ID into `id` on groups/destinations —
// that's not a verb request, so ignore it.
const autoFilled = ctx.caller === 'agent' && args.id === ctx.agentGroupId;
const verbArg = !autoFilled && typeof args.id === 'string' ? args.id : null;
if (verbArg) {
const deep = renderVerbHelp(res, verbArg);
if (!deep) throw new Error(`no verb "${verbArg}" on ${res.plural} — run \`ncl ${res.plural} help\``);
return deep;
}
lines.push(`${res.plural}: ${res.description}`);
if (cliScope === 'group' && GROUP_SCOPE_RESOURCES.has(res.plural)) {
lines.push('');
lines.push('Note: --id and group args are auto-filled to your agent group. You do not need to pass them.');
}
lines.push('');
// Verbs — one summary line each; deep help is a verb away. Only the
// exceptional access levels are tagged: `open` is the unmarked default.
const idAutoFilled = cliScope === 'group' && (res.plural === 'groups' || res.plural === 'destinations');
const idHint = idAutoFilled ? '' : ' <id>';
const tag = (access: string | undefined) => (!access || access === 'open' ? '' : ` [${access}]`);
const verbs: string[] = [];View on GitHub (pinned to 294ef2aee8)
Solutions
- Flatten or delete the deep subtree (most often node_modules or build output — templates shouldn't ship them)
- Add ignore/exclusion handling in your packaging step so deep trees never enter the plugin
- Check MAX_PLUGIN_DEPTH in plugin-dir.ts for the exact limit
Defensive patterns
Strategy: validation
Validate before calling
let maxDepth = 0;
const walk = (d: string, depth: number) => {
maxDepth = Math.max(maxDepth, depth);
for (const e of fs.readdirSync(d, { withFileTypes: true })) {
if (e.isDirectory()) walk(path.join(d, e.name), depth + 1);
}
};
walk(dir, 0);
if (maxDepth > MAX_PLUGIN_DEPTH) { /* prune deep subtrees before walkPluginDir */ } Try / catch
try { walkPluginDir(dir); } catch (e) { if (e instanceof Error && e.message.includes('nesting depth')) { /* delete node_modules/build output and retry */ } else throw e; } Prevention
- Never commit node_modules or build artifacts into templates
- Prune deep trees during packaging with a depth-aware filter
When it happens
Trigger: A plugin whose directory tree nests deeper than the MAX_PLUGIN_DEPTH constant — e.g. deeply nested node_modules chains or generated build artifacts.
Common situations: Committing node_modules or build output into a template; machine-generated trees with pathological nesting; chained extracted archives.
Related errors
- --${column.name.replace(/_/g, '-')} must be a number
- engage_mode '${w.engage_mode}' can never engage on channel '
- chat-sdk bridge instance ${JSON.stringify(config.instance)}
- ${settingsFile} hooks.SessionStart must be an array
- Unknown provider: ${name}. Registered: ${known}
AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28).
Data as JSON: /api/errors/af6baa7eaf690ef5.
Report an issue: GitHub.