{"record":{"id":"d28fc8517d4d705c","repo":"can1357/oh-my-pi","slug":"unknown-message-role","errorCode":null,"errorMessage":"Unknown message role","messagePattern":"Unknown message role","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/providers/amazon-bedrock.ts","lineNumber":961,"sourceCode":"\t\t\t\t\t\ttoolResult: {\n\t\t\t\t\t\t\ttoolUseId: normalizeToolCallId(nextMsg.toolCallId),\n\t\t\t\t\t\t\tcontent: nextMsg.content.map(c =>\n\t\t\t\t\t\t\t\tc.type === \"image\"\n\t\t\t\t\t\t\t\t\t? { image: createImageBlock(c.mimeType, c.data) }\n\t\t\t\t\t\t\t\t\t: { text: c.text.toWellFormed() },\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\tstatus: nextMsg.isError ? \"error\" : \"success\",\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t\tj++;\n\t\t\t\t}\n\t\t\t\ti = j - 1;\n\n\t\t\t\tresult.push({ role: \"user\", content: toolResults });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tthrow new AIError.ValidationError(\"Unknown message role\");\n\t\t}\n\t}\n\n\t// Prioritize the final user checkpoint; buildSystemPrompt consumes any\n\t// remaining configured capacity afterward.\n\tif (result.length > 0) {\n\t\tconst lastMessage = result[result.length - 1];\n\t\tif (lastMessage.role === \"user\" && lastMessage.content) {\n\t\t\tconst cachePoint = takeCachePoint(promptCachePolicy);\n\t\t\tif (cachePoint) (lastMessage.content as UserContent[]).push(cachePoint);\n\t\t}\n\t}\n\n\treturn result;\n}\n\nfunction messagesHaveToolBlocks(messages: WireMessage[]): boolean {\n\tfor (const message of messages) {","sourceCodeStart":943,"sourceCodeEnd":979,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/providers/amazon-bedrock.ts#L943-L979","documentation":"During Bedrock request conversion, each Context message's role is switched over. The converter handles 'user', 'assistant', and the toolResult grouping path; any other role value falls to the default branch and throws a ValidationError. Since Context roles are typed, this normally means a role value outside the expected enum reached the converter (often via untyped/JSON round-tripped data).","triggerScenarios":"Passing a message whose role is not 'user', 'assistant', or a recognized tool-result role into the Bedrock stream/complete call — e.g. a 'system' or 'tool' role supplied as a Context message instead of via the dedicated system option or tool-result message shape.","commonSituations":"Hand-building context arrays and using role 'system' for a system prompt (which must go in the system parameter); deserializing old/foreign transcript JSON with unexpected role strings; provider-agnostic code that emits provider-specific roles.","solutions":["Move system-prompt content into the dedicated system option/parameter instead of a system-role Context message","Express tool outputs using the library's tool-result message format so the converter's grouping branch handles them","Validate/normalize roles when loading transcripts from disk or another provider before passing them as Context","Check for typos or casing drift ('User'/'SYSTEM') in code that constructs messages dynamically"],"exampleFix":"// before\ncontext.push({ role: \"system\", content: [{ type: \"text\", text: sysPrompt }] });\n// after\nconst context: Context[] = [{ role: \"user\", content: [...] }];\nconst system = sysPrompt; // passed as the request's system option","handlingStrategy":"validation","validationCode":"const VALID_ROLES = new Set([\"user\", \"assistant\"]);\nfunction assertValidRoles(context: Context[]): void {\n  for (const m of context) {\n    if (!VALID_ROLES.has(m.role as string)) {\n      throw new Error(`Invalid Context role '${m.role}' — put system prompts in the system option, tool output in tool-result messages`);\n    }\n  }\n}","typeGuard":"function isContextMessage(m: unknown): m is Context {\n  return typeof m === \"object\" && m !== null &&\n    \"role\" in m && ((m as Context).role === \"user\" || (m as Context).role === \"assistant\");\n}","tryCatchPattern":"try {\n  await provider.stream(context);\n} catch (err) {\n  if (err instanceof AIError.ValidationError && err.message === \"Unknown message role\") {\n    // inspect context roles; usually a 'system'/'tool' message misfiled as a Context message\n    logger.error(\"context role error\", { roles: context.map(m => m.role) });\n  }\n  throw err;\n}","preventionTips":["Never use role 'system' in Context arrays — pass system prompts via the system parameter","Type message arrays as Context[] so TS catches invalid role literals at compile time","Validate roles when deserializing transcripts from JSON before use"],"tags":["bedrock","validation","message-role"],"backgroundTag":"invalid-message-role","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}