{"record":{"id":"98f64eb074d97ac6","repo":"mastra-ai/mastra","slug":"factory-rules-tools-toolname-must-be-an-object","errorCode":null,"errorMessage":"Factory rules.tools.${toolName} must be an object.","messagePattern":"Factory rules\\.tools\\.(.+?) must be an object\\.","errorType":"validation","errorClass":"FactoryRuleValidationError","httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/rules/validation.ts","lineNumber":171,"sourceCode":"          throw new FactoryRuleValidationError(`${label}.${stage}.${source} handlers must be functions.`);\n        }\n      }\n    }\n  }\n}\n\nexport function assertFactoryRules(rules: unknown): asserts rules is FactoryRules {\n  if (!isPlainObject(rules)) throw new FactoryRuleValidationError('Factory rules must be an object.');\n  assertExactKeys(rules, ['version', 'work', 'review', 'tools', 'github', 'linear'], 'Factory rules');\n  boundedString(rules.version, 'Factory rule version', MAX_VERSION_LENGTH);\n  validateBoardRules(rules.work, 'Factory rules.work');\n  validateBoardRules(rules.review, 'Factory rules.review');\n\n  if (!isPlainObject(rules.tools)) throw new FactoryRuleValidationError('Factory rules.tools must be an object.');\n  for (const [toolName, leaf] of Object.entries(rules.tools)) {\n    boundedString(toolName, 'Factory tool name', 128, IDENTIFIER_RE);\n    if (!isPlainObject(leaf))\n      throw new FactoryRuleValidationError(`Factory rules.tools.${toolName} must be an object.`);\n    assertExactKeys(leaf, ['onResult'], `Factory rules.tools.${toolName}`);\n    if (leaf.onResult !== undefined && typeof leaf.onResult !== 'function') {\n      throw new FactoryRuleValidationError(`Factory rules.tools.${toolName}.onResult must be a function.`);\n    }\n  }\n\n  if (!isPlainObject(rules.github)) throw new FactoryRuleValidationError('Factory rules.github must be an object.');\n  for (const [event, leaf] of Object.entries(rules.github)) {\n    enumValue(event, FACTORY_GITHUB_EVENTS, 'Factory GitHub event');\n    if (!isPlainObject(leaf)) throw new FactoryRuleValidationError(`Factory rules.github.${event} must be an object.`);\n    assertExactKeys(leaf, ['onEvent'], `Factory rules.github.${event}`);\n    if (leaf.onEvent !== undefined && typeof leaf.onEvent !== 'function') {\n      throw new FactoryRuleValidationError(`Factory rules.github.${event}.onEvent must be a function.`);\n    }\n  }\n\n  if (!isPlainObject(rules.linear)) throw new FactoryRuleValidationError('Factory rules.linear must be an object.');\n  for (const [event, leaf] of Object.entries(rules.linear)) {","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/rules/validation.ts#L153-L189","documentation":"Each entry under rules.tools must be a plain object containing only an optional onResult handler. This error fires when a tool's value is not a plain object (its name already passed boundedString with IDENTIFIER_RE, max 128 chars).","triggerScenarios":"rules.tools.<toolName> set directly to a callback function, null, an array, or a string instead of { onResult: fn }, while validating via assertFactoryRules.","commonSituations":"Writing tools: { runTests: onTestResult } instead of nesting under onResult; null placeholders for tools with no handler; spreads that flattened the expected leaf level.","solutions":["Nest the handler: rules.tools.<toolName> = { onResult: handler }.","Use {} for tools registered without an onResult handler.","Replace null placeholders with {} or delete the entry."],"exampleFix":"// before\ntools: { 'run-tests': onTestResult }\n// after\ntools: { 'run-tests': { onResult: onTestResult } }","handlingStrategy":"type-guard","validationCode":"for (const [name, leaf] of Object.entries(rules.tools ?? {})) {\n  if (/^[a-z0-9][a-z0-9_-]*$/i.test(name) === false) throw new Error(`Invalid tool name: ${name}`);\n  if (leaf === null || typeof leaf !== 'object' || Array.isArray(leaf)) {\n    throw new Error(`tools.${name} must be { onResult?: fn }`);\n  }\n}","typeGuard":"const isToolLeaf = (v: unknown): v is { onResult?: (result: unknown) => unknown } =>\n  typeof v === 'object' && v !== null && !Array.isArray(v);","tryCatchPattern":"try {\n  assertFactoryRules(rules);\n} catch (e) {\n  if (e.name === 'FactoryRuleValidationError' && /rules\\.tools\\..+ must be an object/.test(e.message)) {\n    const tool = e.message.match(/tools\\.(\\S+) must/)?.[1];\n    console.error(`Tool rule for '${tool}' must be { onResult?: fn }`);\n  } else throw e;\n}","preventionTips":["Nest tool handlers under onResult; never assign a bare function as the tool's value.","Type rules.tools as Record<string, { onResult?: ToolHandler }>.","Use {} for tools without result handlers instead of null placeholders."],"tags":["validation","structure","tools","handlers"],"backgroundTag":"schema-validation-failed","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T08:17:16.595Z"}