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).dmView on GitHub (pinned to 294ef2aee8)
Solutions
- Check that <dir>/plugin.json exists exactly with that filename
- If unzipping nested the content, point at the inner directory
- 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
- Validate unpacked plugin directories with a looksLikePlugin check before wiring them in
- Watch for nested folders after archive extraction
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
- Unknown provider: ${name}. Registered: ${known}
- ${settingsFile} hooks must be a JSON object
- ${settingsFile} hooks.SessionStart must be an array
- Channel '${channelKey}' declares engageMode 'pattern' withou
- session_mode 'per-thread' requires honored thread ids, but t
AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28).
Data as JSON: /api/errors/2cf22bd38d540797.
Report an issue: GitHub.