{"record":{"id":"51a5aed3a86727f9","repo":"nanocoai/nanoclaw","slug":"channel-channelkey-declares-engagemode-patte","errorCode":null,"errorMessage":"Channel '${channelKey}' declares engageMode 'pattern' without an engagePattern (${isGroup ? 'group' : 'dm'} context)","messagePattern":"Channel '(.+?)' declares engageMode 'pattern' without an engagePattern \\((.+?) context\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/channels/channel-defaults.ts","lineNumber":98,"sourceCode":"\n  const session: Pick<WiringDefaults, 'session_mode' | 'threads'> = {\n    session_mode: ctx.sessionMode ?? 'shared',\n    // Derived, not declared: per-thread sessions structurally require honored\n    // thread ids, so the stamp is a code invariant of the session mode.\n    threads: ctx.sessionMode === 'per-thread' ? 1 : null,\n  };\n\n  // The effective thread policy for the wiring being created: the derived\n  // stamp wins over the inherit value (mirrors validateEngageAgainstChannel).\n  const effectiveThreads = session.threads === null ? ctx.threads : true;\n\n  let mode = ctx.engageMode;\n  if (mode === 'mention-sticky' && !effectiveThreads) mode = 'mention';\n\n  if (mode !== 'pattern') return { engage_mode: mode, engage_pattern: null, ...session };\n\n  if (!ctx.engagePattern) {\n    throw new Error(\n      `Channel '${channelKey}' declares engageMode 'pattern' without an engagePattern (${isGroup ? 'group' : 'dm'} context)`,\n    );\n  }\n  return {\n    engage_mode: 'pattern',\n    engage_pattern: substituteName(ctx.engagePattern, agentGroupName),\n    ...session,\n  };\n}\n\n/** unknown_sender_policy for a messaging_groups row created in this context.\n *  `channelType` follows the same dead-named-instance discipline as\n *  resolveWiringDefaults. */\nexport function resolveUnknownSenderPolicy(\n  channelKey: string,\n  isGroup: boolean,\n  channelType?: string,\n): 'strict' | 'request_approval' | 'decline_notify' | 'public' {","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/nanocoai/nanoclaw/blob/294ef2aee85218b23ad30eda9dfe10e590b54a8c/src/channels/channel-defaults.ts#L80-L116","documentation":"parseTemplate detected the legacy template layout (a context/instructions.md file exists) but no plugin.json manifest. Old-format templates cannot be parsed by the plugin-era parser; the fix is to re-fetch the template from the library where it has been republished in plugin format.","triggerScenarios":"Passing a directory that contains context/instructions.md but lacks plugin.json — i.e. a pre-plugin-format template directory.","commonSituations":"Upgrading from an older version that shipped the legacy template format and reusing old cached template directories.","solutions":["Re-fetch the template from the template library (it has been republished in plugin format)","Alternatively hand-migrate: add a valid plugin.json manifest and reorganize per the plugin spec","Delete the stale local copy so it isn't picked up again"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const isLegacy = (dir: string) =>\n  fs.existsSync(path.join(dir, 'context', 'instructions.md')) &&\n  !fs.existsSync(path.join(dir, 'plugin.json'));\nif (isLegacy(dir)) { /* trigger re-fetch instead of parseTemplate */ }","typeGuard":null,"tryCatchPattern":"try { parseTemplate(dir); } catch (e) { if (e instanceof Error && e.message.includes('predates the plugin format')) { await refetchTemplate(dir); parseTemplate(dir); } else throw e; }","preventionTips":["After upgrades, sweep template caches for legacy layouts and refresh them","Version-stamp cached templates so stale formats are detectable"],"tags":["templates","migration","version-change"],"backgroundTag":"legacy-format-upgrade","analyzedSha":"294ef2aee85218b23ad30eda9dfe10e590b54a8c","analyzedAt":"2026-08-28T13:59:10.357Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}