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

  1. Flatten or delete the deep subtree (most often node_modules or build output — templates shouldn't ship them)
  2. Add ignore/exclusion handling in your packaging step so deep trees never enter the plugin
  3. 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

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


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