ErrLookupBackground articles › "Wrong argument type", "must be a string", "expected Array or Prism::Scope": TypeError and ArgumentError when a library receives a value of the wrong type

"Wrong argument type", "must be a string", "expected Array or Prism::Scope": TypeError and ArgumentError when a library receives a value of the wrong type

"Wrong argument type" and its many variants ("must be a string", "is not an Integer", "expected Symbol", "Invalid schema") are the TypeError/ArgumentError family that fires when code hands a library a value whose type it never accepts — a string where an integer belongs, an object where a string is required, an array where a single id is expected. Developers meet it at API boundaries: configuration values parsed from JSON/YAML/ENV, deserialized data, framework callbacks, and DSL options where a bare name silently becomes the wrong kind of object. This article explains the shared mechanism across 28 libraries, the most common triggers, and the boundary-validation habits that prevent the whole family.

Distilled from 97 documented records across 28 repositories.

Background

This family sits at the trust boundary between caller and library: a public method declares (explicitly via a type hint, or implicitly via a guard clause) what types it accepts, and the first thing it does with a wrong-typed argument is refuse it. PHP throws TypeError or InvalidArgumentException (yii2's Security::compareString and FileHelper's pattern parser, doctrine/orm's loadMetadataForClass); Ruby raises TypeError or ArgumentError (Prism's FFI backend, concurrent-ruby's atomics, Linguist's Repository); Python raises TypeError (polars' struct indexing); JavaScript/TypeScript and MCP servers throw TypeError on uncoerced JSON arguments (Deno's PerformanceObserver, chroma's rank expressions, ruflo's memory tools). The failure is immediate by design: libraries such as Prism check before any parsing starts, and Puppet's PAL evaluate_string checks at the API boundary precisely so the error does not surface deep inside the parser with a confusing message.

From the caller's side the error almost always means an upstream type drift, not a library bug. The classic sources are configuration and serialized data: JSON and YAML give you strings and numbers where the code assumes a string (concurrent-ruby's Semaphore.new(params['permits']), faker's subscriber_number(length: '4'), Prism's scopes: ["foo"]), and ENV values are always strings (ENV['EXT_LEN'].to_i). A second cluster is object-vs-value confusion in Ruby: passing a Rugged::Commit where the oid String is expected (Linguist), a Pathname where a String is expected (Prism.parse, Bundler::Digest.sha1), or an ActiveRecord array where a single id is required (GitLab's by_group_and_descendants, which is single-id by design because of its covering index). A third cluster is DSL accidentally-wrong-arity: factory_bot's bare `factory: user` (missing colon) dispatches through method_missing and stores a Declaration object where a symbol was expected — the error surfaces only at first build or lint, not at definition time.

The family varies in strictness and in when it fires. Some libraries coerce nothing at all: chroma rejects even numeric strings like "5" in rank arithmetic, and concurrent-ruby rejects whole-number Floats like 3.0. Others coerce selectively or silently degrade: Deno's PerformanceObserver throws on a non-array entryTypes but silently drops unsupported type names, making observe() a no-op. Some checks are warnings today and errors tomorrow — Capybara's locator-type mismatch warns with the caller's backtrace and explicitly says it will raise in a future version. Timing also varies: Prism's guards run before any work starts, yii2's parseExcludePattern is normally shielded by is_string() guards upstream (so hitting it means a subclass bypassed them), and factory_bot defers the check until the first build/create/lint. A few messages are even slightly misleading — faker says lengths must be "lesser than 10" but the code allows exactly 10 — so read the code, not just the message, when the boundary seems off by one.

Common causes

What usually fixes it

Go deeper

Documented occurrences

…and 77 more across the corpus — use search.

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