{"record":{"id":"874c7dbecbb548de","repo":"mastra-ai/mastra","slug":"hook-extractor-name-must-include-an-onextract","errorCode":null,"errorMessage":"Hook extractor \"${name}\" must include an onExtracted handler.","messagePattern":"Hook extractor \"(.+?)\" must include an onExtracted handler\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/memory/src/processors/observational-memory/extractor.ts","lineNumber":155,"sourceCode":"  private readonly schemaConfig?: ExtractorConfigValue<z.ZodType<T> | undefined>;\n\n  constructor(config: ExtractorConfig<T>, internal = false) {\n    const name = config.name.trim();\n    const instructions = typeof config.instructions === 'string' ? config.instructions.trim() : undefined;\n    const slug = slugifyExtractorName(name);\n    const isHook = config.mode === 'hook';\n\n    if (!name) {\n      throw new Error('Extractor name is required.');\n    }\n    if (!isHook && !config.instructions) {\n      throw new Error(`Extractor \"${name}\" must include instructions.`);\n    }\n    if (instructions !== undefined && !instructions) {\n      throw new Error(`Extractor \"${name}\" must include instructions.`);\n    }\n    if (isHook && !config.onExtracted) {\n      throw new Error(`Hook extractor \"${name}\" must include an onExtracted handler.`);\n    }\n    if (isHook && (config.instructions || config.schema)) {\n      throw new Error(`Hook extractor \"${name}\" cannot include instructions or a schema.`);\n    }\n    assertValidSlug(slug, name);\n    if (!internal && RESERVED_XML_TAGS.has(slug)) {\n      throw new Error(`Extractor slug \"${slug}\" is reserved by Observational Memory.`);\n    }\n\n    this.name = name;\n    this.slug = slug;\n    this.instructionsConfig = config.instructions ?? '';\n    this.schemaConfig = config.schema;\n    this.instructions = instructions ?? '';\n    this.schema = (\n      isHook ? z.unknown() : typeof config.schema === 'function' ? z.string() : (config.schema ?? z.string())\n    ) as z.ZodType<T>;\n    this.mode = isHook ? 'hook' : internal || !config.schema ? 'inline' : 'structured';","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/memory/src/processors/observational-memory/extractor.ts#L137-L173","documentation":"Hook extractors run as post-extraction callbacks and do not participate in the extraction prompt, so they have no use for `instructions` but must provide an `onExtracted` handler to do their work. The library throws this in the constructor when `mode: 'hook'` is set but `config.onExtracted` is missing or falsy.","triggerScenarios":"new Extractor({ name: 'Audit', mode: 'hook' }) with no onExtracted; onExtracted explicitly set to undefined/null; typo in the property name (e.g. onExtract) so the handler is never seen.","commonSituations":"Converting a normal extractor to hook mode by only changing `mode`; copying a config type but forgetting the callback; conditionally omitting onExtracted with a spread that drops the key.","solutions":["Provide an `onExtracted` callback that receives ExtractorOnExtractedContext and returns the (possibly transformed) value.","If the extractor should drive the LLM prompt instead, remove `mode: 'hook'` and add `instructions`.","Verify the property is spelled `onExtracted` and is not being stripped by a spread/conditional."],"exampleFix":"// before\nnew Extractor({ name: 'Audit Log', mode: 'hook' });\n// after\nnew Extractor({\n  name: 'Audit Log',\n  mode: 'hook',\n  onExtracted: ({ current }) => {\n    logger.info('extracted', current);\n  },\n});","handlingStrategy":"validation","validationCode":"if (config.mode === 'hook' && typeof config.onExtracted !== 'function') {\n  throw new Error(`Hook extractor \"${config.name}\" needs onExtracted`);\n}","typeGuard":"function isValidHookExtractor(c) {\n  return c.mode !== 'hook' || typeof c.onExtracted === 'function';\n}","tryCatchPattern":"try {\n  const extractor = new Extractor(config);\n} catch (e) {\n  if (e.message.includes('onExtracted handler')) {\n    logger.error('Hook extractor missing onExtracted', { name: config.name });\n  } else { throw e; }\n}","preventionTips":["Treat mode: 'hook' and onExtracted as an inseparable pair; define them together in a helper.","Beware property typos (onExtract vs onExtracted) — typecheck with the exact ExtractorConfig type.","Avoid conditional spreads that can silently drop onExtracted."],"tags":["config","callback","observational-memory"],"backgroundTag":"missing-required-config-option","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}