nanocoai/nanoclaw · error

${NANOCLAW_EXTENSION_NS}/context/instructions.md must be a r

Error message

${NANOCLAW_EXTENSION_NS}/context/instructions.md must be a regular file

What it means

When reading a NanoClaw plugin/template extension, the loader expects <extension-ns>/context/instructions.md to be a regular file. It exists (fs.existsSync passes) but lstatSync().isFile() is false — meaning it is a symlink, directory, FIFO, or other special file — so the extension is rejected as malformed.

Source

Thrown at src/templates/extension.ts:70

          report.push(
            `plugin.json: extensions["${NANOCLAW_EXTENSION_NS}"].agentName must be a nonempty string; ignored`,
          );
      }
      for (const key of Object.keys(ours)) {
        if (key !== 'agentName') {
          report.push(`plugin.json: extensions["${NANOCLAW_EXTENSION_NS}"].${key} is not recognized; ignored`);
        }
      }
    }
  }

  const extDir = path.join(pluginDir, NANOCLAW_EXTENSION_NS);
  const contextDir = path.join(extDir, 'context');
  const instructionsFile = path.join(contextDir, 'instructions.md');
  let instructions: string | undefined;
  if (fs.existsSync(instructionsFile)) {
    if (!fs.lstatSync(instructionsFile).isFile()) {
      throw new Error(`${NANOCLAW_EXTENSION_NS}/context/instructions.md must be a regular file`);
    }
    instructions = fs.readFileSync(instructionsFile, 'utf-8').trimEnd();
  }

  return {
    ...(agentName === undefined ? {} : { agentName }),
    ...(instructions === undefined ? {} : { instructions }),
    contextExtras: readContextExtras(contextDir),
    tasks: readTasks(path.join(extDir, 'tasks'), `${NANOCLAW_EXTENSION_NS}/tasks`),
    report,
  };
}

/**
 * Every context/**\/*.md except the top-level instructions.md, recursively.
 * `name` keeps the path relative to context/ so stamping can preserve the
 * layout — a reference like `additional_context/faq.md` written in
 * instructions.md resolves unchanged in the agent's workspace.

View on GitHub (pinned to 294ef2aee8)

Solutions

  1. Replace the symlink with a real file: cp -L symlink temp && mv temp instructions.md (or remove dotfile stowing for this path).
  2. If it is a directory, remove it and create a regular file with the instruction text.
  3. If sharing content across templates is the goal, duplicate the file or generate it at build time instead of symlinking.

Example fix

# before
context/instructions.md -> ../../shared-instructions.md  (symlink)

# after
cp -L ../../shared-instructions.md context/instructions.md
Defensive patterns

Strategy: validation

Validate before calling

import * as fs from 'node:fs';
const p = path.join(extDir, 'context', 'instructions.md');
if (fs.existsSync(p) && !fs.statSync(p).isFile()) throw new Error('instructions.md is not a regular file — fix the template layout');

Type guard

const isRegularFile = (p: string): boolean => { try { return fs.lstatSync(p).isFile(); } catch { return false; } };

Try / catch

try { ext = readNanoclawExtension(pluginDir); } catch (err) { if ((err as Error).message.includes('must be a regular file')) throw new Error('Template instructions.md is a symlink/dir — replace with a real file', { cause: err }); throw err; }

Prevention

When it happens

Trigger: instructions.md being a symlink (e.g. dotfiles-managed config, ln -s into a repo), a directory accidentally created at that path, or a broken symlink that happens to satisfy existsSync via a target somewhere.

Common situations: Users managing templates with GNU stow or symlinking a shared instructions file across templates; a mistaken 'mkdir instructions.md'; package managers that materialize symlinks.

Related errors


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