ErrLookupBackground articles › Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained

Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained

"The 'options' argument must be of type object", "needs to be a string", "expected string, got ..." — invalid argument type errors fire when a library's entry-point validation rejects the type of an argument you passed, before any real work starts. Developers meet this family when calling browser automation APIs (k6 selectOption, setInputFiles), LLM gateways (litellm api_base, dynamic_api_key, usage and timestamp objects), runtime polyfills (Deno's ERR_INVALID_ARG_TYPE family), plugin sandboxes (siyuan registerCapability, port.send), and data clients (chroma ids, turso connections). This article covers the shared mechanism behind 125 documented records across 15 repositories, the most common causes, and the fixes that hold across the whole family: normalize types at the boundary, pass the library's exact accepted shape, and unwrap wrapper types before the call.

Distilled from 125 documented records across 15 repositories.

Background

These errors come from fail-fast validation at a library's public boundary, not from work going wrong mid-operation. Node-style runtimes centralize the pattern: Deno's polyfills throw ERR_INVALID_ARG_TYPE from dedicated validators before a single field is read (spawn options, cipher.update data, timingSafeEqual buffers, Worker env). Other libraries embed the check in a conversion step at a language boundary: k6's Go-side converters (Files.Parse for setInputFiles descriptors, ConvertSelectOptionValues for selectOption values) reject any shape Go cannot map; siyuan's Go kernel applies goja type assertions (IsString, AssertFunction, isJsValueNotNull) to every value crossing out of the plugin sandbox; litellm guards scalar options in get_llm_provider and normalizes logging-payload fields with strict isinstance checks. In every case the throw is a contract statement: the value you handed over is not on the accepted-type list, and the library refused it before touching it.

The narrowness is deliberate. These values are headed somewhere unforgiving: api_base and dynamic_api_key get concatenated into request URLs and Authorization headers in litellm's provider handlers; base paths and directory-entry names get string- or Buffer-concatenated in Deno's readdir join; serializer arrays and selectOption values get iterated element by element; k6 descriptors and tree-sitter's matchLimit cross into Go and wasm. Rejecting early with the offending type echoed back ("got %T", "Received undefined", "of type <class 'int'>", "Was given a function") replaces an obscure downstream crash with a local, actionable one. That is why the messages read as type reports rather than operation failures.

From the caller's seat the message shapes vary — "The \"env\" property must be of type object", "options[value]: expected string, got int", "usage is required, got=... of type ...", "Cannot get raw connection from SQLAlchemy connection" — but the diagnosis is identical: find the accepted-shape list for that argument and match it. Strictness itself is library-specific and sometimes path-specific: chroma auto-wraps a single value into a one-element list through the public collection API but rejects tuples and generators in the direct validator; Deno's Worker env option String()-coerces values inside an object yet rejects a bare string; Deno's timingSafeEqual accepts a broader Buffer/ArrayBuffer/TypedArray/DataView union than Node's Buffers-only API; k6's setInputFiles rejects the plain file paths Playwright accepts and requires base64 descriptor objects. Porting calls verbatim between similar libraries is one of the most reliable ways to meet this family.

The subtlest members involve near-miss types rather than obviously wrong ones. An openai SDK CompletionUsage duck-types identically to litellm's Usage but fails the isinstance check; a plain dict fails the ImageResponse check in litellm's image-cost calculator; a plain JavaScript object fails Immutable.Map.isMap in swagger-ui's paramToIdentifier; a Key instance fails instanceof when two copies of the chromadb package are loaded. Edge behaviors compound the confusion: falsy-but-not-nullish values such as 0 and false sail past a ?? fallback straight into the typeof check, Python ints fail validators that accept only floats, and NaN passes typeof 'number' in tree-sitter's matchLimit check. These guards enforce type identity, not type resemblance — duck typing does not help you here.

Common causes

What usually fixes it

Documented occurrences

…and 105 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.