can1357/oh-my-pi · error · ValidationError

Unknown message role

Error message

Unknown message role

What it means

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).

Source

Thrown at packages/ai/src/providers/amazon-bedrock.ts:961

						toolResult: {
							toolUseId: normalizeToolCallId(nextMsg.toolCallId),
							content: nextMsg.content.map(c =>
								c.type === "image"
									? { image: createImageBlock(c.mimeType, c.data) }
									: { text: c.text.toWellFormed() },
							),
							status: nextMsg.isError ? "error" : "success",
						},
					});
					j++;
				}
				i = j - 1;

				result.push({ role: "user", content: toolResults });
				break;
			}
			default:
				throw new AIError.ValidationError("Unknown message role");
		}
	}

	// Prioritize the final user checkpoint; buildSystemPrompt consumes any
	// remaining configured capacity afterward.
	if (result.length > 0) {
		const lastMessage = result[result.length - 1];
		if (lastMessage.role === "user" && lastMessage.content) {
			const cachePoint = takeCachePoint(promptCachePolicy);
			if (cachePoint) (lastMessage.content as UserContent[]).push(cachePoint);
		}
	}

	return result;
}

function messagesHaveToolBlocks(messages: WireMessage[]): boolean {
	for (const message of messages) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Move system-prompt content into the dedicated system option/parameter instead of a system-role Context message
  2. Express tool outputs using the library's tool-result message format so the converter's grouping branch handles them
  3. Validate/normalize roles when loading transcripts from disk or another provider before passing them as Context
  4. Check for typos or casing drift ('User'/'SYSTEM') in code that constructs messages dynamically

Example fix

// before
context.push({ role: "system", content: [{ type: "text", text: sysPrompt }] });
// after
const context: Context[] = [{ role: "user", content: [...] }];
const system = sysPrompt; // passed as the request's system option
Defensive patterns

Strategy: validation

Validate before calling

const VALID_ROLES = new Set(["user", "assistant"]);
function assertValidRoles(context: Context[]): void {
  for (const m of context) {
    if (!VALID_ROLES.has(m.role as string)) {
      throw new Error(`Invalid Context role '${m.role}' — put system prompts in the system option, tool output in tool-result messages`);
    }
  }
}

Type guard

function isContextMessage(m: unknown): m is Context {
  return typeof m === "object" && m !== null &&
    "role" in m && ((m as Context).role === "user" || (m as Context).role === "assistant");
}

Try / catch

try {
  await provider.stream(context);
} catch (err) {
  if (err instanceof AIError.ValidationError && err.message === "Unknown message role") {
    // inspect context roles; usually a 'system'/'tool' message misfiled as a Context message
    logger.error("context role error", { roles: context.map(m => m.role) });
  }
  throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/d28fc8517d4d705c. Report an issue: GitHub.