Hmbown/CodeWhale · error · RuntimeContractError

tool execution_shell must be `bash`, got

Error message

{kind} tool execution_shell must be `bash`, got {shell!r}

What it means

The contract requires the tool catalog to declare bash as its execution shell; any other value makes the measurement non-reproducible against the pinned contract. validate_identity_structure compares tool_catalog.execution_shell to the literal "bash" and throws otherwise.

Solutions

  1. Set tool_catalog.execution_shell to "bash" in the document
  2. Regenerate the receipt in a bash environment using the current script
  3. If a non-bash shell is genuinely required, that is a contract change: update the script and all receipts together

Example fix

// before
{"tool_catalog": {"execution_shell": "sh"}}
// after
{"tool_catalog": {"execution_shell": "bash"}}
Defensive patterns

Strategy: validation

Validate before calling

assert doc.get("tool_catalog", {}).get("execution_shell") == "bash", \
    "receipt must declare bash as execution_shell"

Try / catch

try:
    validate_budget(budget)
except RuntimeContractError as e:
    if "execution_shell" in str(e):
        raise SystemExit("receipt generated for a non-bash shell; regenerate in bash")
    raise

Prevention

When it happens

Trigger: validate_receipt/validate_budget reading a document whose tool_catalog.execution_shell is not exactly "bash" (e.g. "sh", "zsh", null).

Common situations: Receipt produced on/for a different shell environment; hand-edited document; generator configured with a non-bash shell.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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

Appendix: source

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

        value = value[part]
    return value


def tool_identity_digest(names: list[str]) -> str:
    return hashlib.sha256("\0".join(names).encode("utf-8")).hexdigest()


def validate_identity_structure(document: dict[str, Any], kind: str) -> None:
    profile = required_value(document, ("tool_catalog", "surface_profile"), kind)
    if profile != TOOL_SURFACE_PROFILE:
        raise RuntimeContractError(
            f"{kind} tool surface_profile must be `{TOOL_SURFACE_PROFILE}`, "
            f"got {profile!r}"
        )

    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):

View on GitHub (pinned to 433685b202)