nanocoai/nanoclaw · error

engage_mode 'pattern' requires --engage-pattern (use "." to

Error message

engage_mode 'pattern' requires --engage-pattern (use "." to match every message)

What it means

The directory exists but has no plugin.json and no legacy marker (context/instructions.md), so it is not recognizable as an agent plugin at all. parseTemplate refuses to guess.

Source

Thrown at src/channels/channel-defaults.ts:169

 * wiring-creation surface (`ncl wirings` create/update, the setup wizard's
 * register step) so a partial update or an explicit flag can't produce a
 * combination create would reject — including session_mode 'per-thread' on a
 * wiring whose thread policy resolves off. May mutate `w.engage_mode`: the
 * mention-sticky→mention coercion when the effective thread policy is off —
 * sticky engagement is keyed on per-thread session existence, so without
 * thread ids it would engage once and never disengage.
 *
 * Declaration-derived checks are gated on hasDeclaredChannelDefaults: stale
 * (undeclared) adapters keep the legacy lenient behavior — the fallback
 * declaration is permissive on mentions but its threads value is false when
 * no adapter is live, which would wrongly coerce offline-created wirings.
 */
export function validateEngageAgainstChannel(w: EngageValues, mg: MessagingGroup): void {
  if (
    w.engage_mode === 'pattern' &&
    (w.engage_pattern === undefined || w.engage_pattern === null || w.engage_pattern === '')
  ) {
    throw new Error(`engage_mode 'pattern' requires --engage-pattern (use "." to match every message)`);
  }

  // per-thread sessions structurally require honored thread ids — reject the
  // incoherent combination rather than storing it. Creation paths that resolve
  // through the declaration derive threads=1 from sessionMode 'per-thread'
  // (resolveWiringDefaults), so only explicit flags can reach this: an
  // explicit threads=false, or NULL-inherit on a context whose declared
  // `threads` is false. Undeclared (stale) adapters stay lenient on the
  // inherit arm, same as the mention checks below.
  if (w.session_mode === 'per-thread') {
    const key = mg.instance ?? mg.channel_type;
    const explicit = w.threads !== undefined && w.threads !== null;
    const honored = explicit
      ? w.threads !== 0 && w.threads !== false
      : !hasDeclaredChannelDefaults(key, mg.channel_type) ||
        (mg.is_group === 1
          ? getChannelDefaults(key, mg.channel_type).group
          : getChannelDefaults(key, mg.channel_type).dm

View on GitHub (pinned to 294ef2aee8)

Solutions

  1. Check that <dir>/plugin.json exists exactly with that filename
  2. If unzipping nested the content, point at the inner directory
  3. If building a new plugin, create a plugin.json manifest first
Defensive patterns

Strategy: validation

Validate before calling

if (!fs.existsSync(path.join(dir, 'plugin.json'))) {
  // wrong dir or not a plugin: fail before parseTemplate
}

Type guard

function looksLikePlugin(dir: string): boolean {
  return fs.existsSync(path.join(dir, 'plugin.json'));
}

Try / catch

try { parseTemplate(dir); } catch (e) { if (e instanceof Error && e.message.startsWith('Not an agent plugin')) { /* locate the manifest or reject the dir */ } else throw e; }

Prevention

When it happens

Trigger: Passing an arbitrary directory, an empty folder, or a plugin whose manifest was renamed/misspelled (e.g. plugin-manifest.json).

Common situations: Wrong directory passed (parent of the plugin, or a sibling); manifest file deleted or misnamed; unzip produced a nested folder so the actual plugin is one level deeper.

Related errors


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