JuliusBrussee/caveman · error · CatalogError
: must be a safe non-empty identifier
Error message
{label}: {field} must be a safe non-empty identifier What it means
One of provider, model, or region is not a string matching the IDENTIFIER regex `^[A-Za-z0-9][A-Za-z0-9._:@/-]*$` — i.e. not a safe, non-empty, alphanumeric-leading identifier. These three fields form the row's identity (used for duplicate detection and snapshot pinning), so unsafe characters or empty values are rejected. Thrown from validate_row.
Solutions
- Use the canonical identifier form: start with a letter/digit, then only letters, digits, and `._:@/-` (e.g. `gpt-4o`, `us-east-1`)
- Replace spaces with hyphens and drop or replace disallowed characters
- Ensure the value is a non-empty string, not null or a number
- Match the exact identifier used elsewhere in the catalog for the same provider/model/region
Example fix
# before model: "gpt 4o (turbo)" # after model: gpt-4o
Defensive patterns
Strategy: validation
Validate before calling
import re
IDENTIFIER = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:@/-]*$")
def identity_fields_ok(row: dict) -> bool:
return all(isinstance(row.get(f), str) and IDENTIFIER.fullmatch(row[f])
for f in ("provider", "model", "region")) Type guard
def is_safe_identifier(value: object) -> bool:
return isinstance(value, str) and bool(IDENTIFIER.fullmatch(value)) Try / catch
try:
validate_catalog()
except CatalogError as e:
if "safe non-empty identifier" in str(e):
sanitize_identity_field(str(e)) Prevention
- Use canonical short names (gpt-4o, us-east-1), never display names or URLs
- Run the IDENTIFIER regex over script-generated values before writing YAML
- Check for empty strings when values come from env vars or templates
When it happens
Trigger: A row has an empty string, a leading special character (`-anthropic`), spaces (`"gpt 4o"`), non-ASCII characters, or a non-string value (null, number) for provider/model/region.
Common situations: Pasting a display name with spaces or uppercase-mangled characters; a script substituting an empty value for a missing env var; using a URL as the provider; quoting quirks leaving a null where a name was expected.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- catalog
- : currency must be USD
- : catalog must be a non-empty list
- catalog is empty
- caveman agent: invalid agent id
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/90257a76afd8403b.
Report an issue: GitHub.
Appendix: source
Thrown at shared/provider-catalog/validate_catalog.py:128
raise CatalogError(f"{label}: verified_at must be an RFC3339 timestamp")
if parsed.tzinfo is None:
raise CatalogError(f"{label}: verified_at must include a timezone")
return parsed.astimezone(timezone.utc)
def validate_row(row: dict[str, Any], label: str, now: datetime) -> tuple[str, str, str]:
unknown = set(row) - TOP_LEVEL_KEYS
missing = REQUIRED_KEYS - set(row)
if unknown:
raise CatalogError(f"{label}: unknown field(s): {', '.join(sorted(unknown))}")
if missing:
raise CatalogError(f"{label}: missing field(s): {', '.join(sorted(missing))}")
identity: list[str] = []
for field in ("provider", "model", "region"):
value = row[field]
if not isinstance(value, str) or not IDENTIFIER.fullmatch(value):
raise CatalogError(f"{label}: {field} must be a safe non-empty identifier")
identity.append(value)
if row["currency"] != "USD":
raise CatalogError(f"{label}: currency must be USD")
pricing = row["pricing"]
if not isinstance(pricing, dict):
raise CatalogError(f"{label}: pricing must be an object")
pricing_unknown = set(pricing) - PRICING_KEYS
if pricing_unknown:
raise CatalogError(f"{label}: unknown pricing field(s): {', '.join(sorted(pricing_unknown))}")
for required in ("input_per_million", "output_per_million"):
if required not in pricing:
raise CatalogError(f"{label}: pricing.{required} is required")
for field, value in pricing.items():
if value is None or field == "long_context_threshold_inclusive":
if field == "long_context_threshold_inclusive" and value is not None and not isinstance(value, bool):
raise CatalogError(f"{label}: pricing.{field} must be boolean or null")
continueView on GitHub (pinned to 3ee70a1026)