Hmbown/CodeWhale · error · RuntimeContractError
metric ` ` must equal the owned tool_names length ( )
Error message
{kind} metric `{'.'.join((*base, 'tools'))}` must equal the owned tool_names length ({len(names)}) What it means
Each mode/surface section carries a tools count metric that must equal len(tool_names) in the same section. This cross-check catches drift between the human-readable list and the numeric metric used by budget comparisons.
Solutions
- Set tools to exactly len(tool_names) for that section
- Regenerate the receipt so both are written from the same source of truth
- Fix the generator to derive the count from the list rather than tracking it separately
Example fix
// before "tool_names": ["a", "b"], "tools": 3 // after "tool_names": ["a", "b"], "tools": 2
Defensive patterns
Strategy: validation
Validate before calling
for mode, _ in VISIBLE_MODES:
for surface, _ in TOOL_SURFACES:
sec = doc["tool_catalog"]["modes"][mode][surface]
assert sec["tools"] == len(sec["tool_names"]), f"count drift in {mode}/{surface}" Try / catch
try:
compare(receipt, budget)
except RuntimeContractError as e:
if "must equal the owned tool_names length" in str(e):
print("count/list drift; regenerate the receipt")
raise Prevention
- Derive the tools count from len(tool_names) in the generator — never track it separately
- Never hand-edit one side of the count/list pair
- Re-run the validator after any merge that touched tool lists
When it happens
Trigger: metric_value(document, (*base, "tools"), kind) returns a number different from the length of the sibling tool_names list — e.g. the count was updated but the list was not, or vice versa.
Common situations: Hand-edited receipts where a tool was added to the list but the count not incremented; two generators writing list and count at different times; merge conflict resolved on one side only.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Codewhale metadata event was not a terminal receipt
- field ` ` must be sorted unique non-empty strings
- field ` ` must match the owned sorted tool_names
- is missing required field
- tool execution_shell must be `bash`, got
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/e82ca4903cf483af.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/check-runtime-contract-budget.py:234
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")
digest = required_value(document, path, kind)
if not isinstance(digest, str) or re.fullmatch(r"[0-9a-f]{64}", digest) is None:
raise RuntimeContractError(
f"{kind} field `{'.'.join(path)}` must be a lowercase SHA-256 digest"
)View on GitHub (pinned to 433685b202)