Hmbown/CodeWhale · error · RuntimeContractError

field ` ` must be sorted unique non-empty strings

Error message

{kind} field `{dotted_names}` must be sorted unique non-empty strings

What it means

For each visible mode/tool-surface pair, the owned tool_names list must be a list of non-empty strings, sorted, and free of duplicates. validate_identity_structure enforces this so the identity digest (error 805) is deterministic. Anything unsorted, duplicated, empty, or non-string fails.

Solutions

  1. Sort the list and deduplicate: names = sorted(set(names)) in the generator before writing
  2. Remove empty or non-string entries from tool_names
  3. Regenerate the receipt with the current generator instead of editing by hand

Example fix

// before
"tool_names": ["read", "edit", "read"]
// after
"tool_names": ["edit", "read"]
Defensive patterns

Strategy: validation

Validate before calling

names = doc["tool_catalog"]["modes"][mode][surface]["tool_names"]
ok = (isinstance(names, list)
      and all(isinstance(n, str) and n for n in names)
      and names == sorted(set(names)))
assert ok, "tool_names must be sorted unique non-empty strings"

Type guard

def valid_tool_names(v) -> bool:
    return (isinstance(v, list)
            and all(isinstance(n, str) and n for n in v)
            and v == sorted(set(v)))

Try / catch

try:
    validate_receipt(receipt)
except RuntimeContractError as e:
    if "sorted unique non-empty strings" in str(e):
        print("regenerate: generator emitted unsorted or duplicated tool_names")
    raise

Prevention

When it happens

Trigger: A tool_catalog.modes.<mode>.<surface>.tool_names array in a receipt/budget that is unsorted, contains duplicates, contains empty strings, or contains non-string entries.

Common situations: Generator appends tools in discovery order rather than sorted; duplicate registration of a tool; a tool with an empty name after a rename; hand-edited list.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/d988d5b824e8d25e. Report an issue: GitHub.

Appendix: source

Thrown at scripts/check-runtime-contract-budget.py:229

        )

    shell = required_value(document, ("tool_catalog", "execution_shell"), kind)
    if shell != "bash":
        raise RuntimeContractError(
            f"{kind} tool execution_shell must be `bash`, got {shell!r}"
        )

    for mode, _label in VISIBLE_MODES:
        for surface, _surface_label in TOOL_SURFACES:
            base = ("tool_catalog", "modes", mode, surface)
            names = required_value(document, (*base, "tool_names"), kind)
            dotted_names = ".".join((*base, "tool_names"))
            if (
                not isinstance(names, list)
                or any(not isinstance(name, str) or not name for name in names)
                or names != sorted(set(names))
            ):
                raise RuntimeContractError(
                    f"{kind} field `{dotted_names}` must be sorted unique non-empty strings"
                )
            count = metric_value(document, (*base, "tools"), kind)
            if count != len(names):
                raise RuntimeContractError(
                    f"{kind} metric `{'.'.join((*base, 'tools'))}` must equal the "
                    f"owned tool_names length ({len(names)})"
                )
            digest = required_value(document, (*base, "identity_sha256"), kind)
            expected = tool_identity_digest(names)
            if digest != expected:
                raise RuntimeContractError(
                    f"{kind} field `{'.'.join((*base, 'identity_sha256'))}` must "
                    "match the owned sorted tool_names"
                )

    for stage, _label in REPRESENTATIVE_STAGES:
        path = ("representative_context", "stages", stage, "identity_sha256")

View on GitHub (pinned to 433685b202)