headroomlabs-ai/headroom · error · TypeError
unserializable config value: {type(value).__name__}
Error message
unserializable config value: {type(value).__name__} What it means
Raised by _json_default in config_compressor.py when JSON-serializing parsed TOML content encounters a value that is neither a TOML date/time nor a JSON-native type. The hook renders dt.datetime/dt.date/dt.time as ISO strings and treats everything else (e.g. tomllib's returned types that stray from the allowlist) as unserializable, failing loudly instead of emitting lossy config.
Source
Thrown at headroom/transforms/config_compressor.py:79
"""Parse TOML with the stdlib parser (or the tomli backport); None on error."""
try:
import tomllib
except ModuleNotFoundError: # pragma: no cover - Python < 3.11 only
try:
import tomli as tomllib # type: ignore[no-redef]
except ModuleNotFoundError:
return None
try:
return cast("dict[str, Any]", tomllib.loads(content))
except (tomllib.TOMLDecodeError, ValueError):
return None
def _json_default(value: Any) -> str:
"""Render TOML date/time values as ISO strings; bail on anything else."""
if isinstance(value, dt.datetime | dt.date | dt.time):
return value.isoformat()
raise TypeError(f"unserializable config value: {type(value).__name__}")
@dataclass
class ConfigCompressorConfig:
"""Configuration for structured-config compression."""
# Emit the CCR-marked comment/blank elision tier. The router wires this
# to its ccr_inject_marker setting; lossless mode turns it off.
enable_ccr: bool = True
# Bridge TOML array-of-tables to SmartCrusher csv-schema (Tier 3). Rides
# CCR for recovery, so it only runs when enable_ccr is also on.
enable_schema_fold: bool = True
# Only adopt a result that is strictly smaller than the original.
min_savings_chars: int = 1
@dataclass
class ConfigCompressionResult:View on GitHub (pinned to 322425c43b)
Solutions
- Inspect the TypeError's type name to find the offending key in the TOML, and replace that value with a string/number/bool.
- If the type is legitimately serializable, extend _json_default with an isinstance branch for it (e.g. Decimal -> str(value)).
- Keep config files to plain TOML scalars, arrays, and tables.
Example fix
# before
def _json_default(value):
if isinstance(value, dt.datetime | dt.date | dt.time):
return value.isoformat()
raise TypeError(f"unserializable config value: {type(value).__name__}")
# after — add a branch for the new type
def _json_default(value):
if isinstance(value, dt.datetime | dt.date | dt.time):
return value.isoformat()
if isinstance(value, decimal.Decimal):
return str(value)
raise TypeError(f"unserializable config value: {type(value).__name__}") Defensive patterns
Strategy: try-catch
Try / catch
try:
payload = json.dumps(config_dict, default=_json_default)
except TypeError as e:
if "unserializable config value" in str(e):
locate_offending_key(config_dict) # walk dict, test json.dumps per leaf
raise ConfigError(f"config contains unsupported value: {e}") from e
raise Prevention
- Keep TOML configs to scalars, arrays, and tables only.
- Extend _json_default when introducing a new serializable value type.
- Validate configs by round-tripping (parse -> dump) in CI.
When it happens
Trigger: Serializing a TOML config that contains a value type _json_default does not whitelist — any non date/time custom object reaching json.dumps(default=_json_default) during structured-config compression.
Common situations: A TOML file with exotic inline values, or a code change that puts non-TOML objects into the dict before serialization; version drift in tomllib's returned types.
Related errors
- {env_var} must be a JSON object of header name/value strings
- invalid pipeline config TOML: {0}
- recommendations file not found: {0}
- recommendations IO error at {path}: {source}
- bedrock_eventstream_parse_failed
AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15).
Data as JSON: /api/errors/fa6f39dcf42da113.
Report an issue: GitHub.