ErrLookupBackground articles › Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list

Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list

Invalid enum value errors ("Unknown worker type", "Invalid scope", "X must be one of ...", "is not a valid mode") fire when a string argument, configuration value, or stored field does not exactly match one of the closed set of literals a library accepts. Developers meet this family on typos and case mismatches, on values ported from sibling frameworks (Playwright wait states, sentence-transformers' 'passage'), on unvalidated environment and config overrides, and on database rows written without validation. This article explains the mechanism shared by the 85 documented records across 23 repositories, how the failure surface differs per library, and the remediation themes that hold across the family.

Distilled from 85 documented records across 23 repositories.

Background

Every record in this family comes from the same shape of code: a closed set of literals defined inside the library, and an exact comparison at the point of use. Mechanically it is a switch or match expression whose default arm throws (ruflo's worker and transport types, PhpSpreadsheet's sheetview layouts and explicit datatypes, SiYuan's heading fold scope), a map or registry lookup that misses (PhpSpreadsheet's dynamic-filter date functions, career-ops' template kinds, ruflo's completion modes), or an explicit allowlist test ahead of a write (Rocket.Chat's role scopes and outgoing-integration events, anything-llm's roles, k6's permission names). The set is closed because the literals select behavior, not presentation: chroma's BM25 embedding function dispatches 'document' to corpus indexing and 'query' to a separate path with different term statistics, SiYuan's fold scope chooses between two different transactions, and k6's waitUntil values map to concrete browser lifecycle events. From the caller's side the experience is uniform: a string that looks right is rejected because it is not the exact literal, and the comparison is case-sensitive and whitespace-sensitive almost everywhere in the family.

Direct argument typos are the visible surface, but the records show three deeper channels. Values cross library boundaries carrying the wrong vocabulary: Playwright's 'enabled' and 'stable' wait states are rejected by k6, which supports only 'attached', 'detached', 'visible' and 'hidden'; sentence-transformers' 'passage' is rejected by chroma, which wants 'document'; chat-ui's 'legacy_completions' is rejected by ruflo. Values enter through configuration that never sees the library's validation: environment variables such as TRANSPORT=ws and FOCUS_FREQUENCY=weekly, JSON5 overrides cast to any, CLI flags. And values arrive from storage written without the check: an embed row whose chat_mode was edited directly in the database fails every subsequent request, Mongo user documents with free-text statusConnection warn on every decode, and turso rejects enum members persisted by a release that defined a different set.

Static types do not close the gap. TypeScript's TransportType union stops invalid literals only for checked callers; casts, JavaScript callers and config strings reach the factory and throw at runtime. Python's Literal hint on chroma's task parameter is documentation, not enforcement: __init__ accepts anything and the error fires at call time. A zod schema guards ruflo's endpoint options, but an object passed without schema.parse bypasses it and reaches the final else. Where a guard does exist, the failure can also mean guard drift: appwrite's 'Unknown queue name' 500 appears only when the whitelist that validated the request and the match keys no longer agree, typically because _APP_*_QUEUE_NAME environment values changed after the route table was built.

What happens after the check varies by library, so treat severity as library-specific. Most members reject before any work happens; validation runs ahead of database writes, so nothing is persisted and a corrected retry is safe. Exceptions exist: Rocket.Chat's user codec logs a warning and passes the bad statusConnection through, and career-ops' tracker merge warns and then silently records the row under the wrong canonical state. Message quality also varies: some errors interpolate the full allowed list, so the correction is already in the text; some echo only the rejected value; and neon's scheduling-policy hint omits one accepted literal ('activating') while spelling 'decomissioned' with a single m, matching the code rather than the dictionary.

Common causes

What usually fixes it

Documented occurrences

…and 65 more across the corpus — use search.

Honest provenance: generated on 2026-08-20 from AI-assisted analysis of the linked records. See how records are made.