Hmbown/CodeWhale · error · ValueError

disabled_tools must use Codewhale catalog identifiers: ${",

Error message

disabled_tools must use Codewhale catalog identifiers: ${", ".join(repr(tool) for tool in invalid)}

What it means

Every entry in config.disabled_tools must fullmatch ^[a-z0-9][a-z0-9_-]*$ — a lowercase Codewhale catalog identifier starting with an alphanumeric. The offending entries are interpolated into the message with repr(), so the error names exactly which strings failed. The list is joined into --disallowed-tools on the exec command line, so uppercase, spaces, dots, or a leading dash break the CLI contract.

Source

Thrown at integrations/verifiers-codewhale/codewhale_harness/harness.py:110

    APPENDS_SYSTEM_PROMPT = True
    SUPPORTS_MCP = True
    SUPPORTS_USER_SIM = False

    def _validate_config(self) -> None:
        if not _VERSION.fullmatch(self.config.version):
            raise ValueError("version must be a semantic release identifier")
        if (
            self.config.max_turns is not None
            and not 1 <= self.config.max_turns <= 10_000
        ):
            raise ValueError("max_turns must be between 1 and 10000")
        invalid = [
            tool
            for tool in self.config.disabled_tools or []
            if not _TOOL.fullmatch(tool)
        ]
        if invalid:
            raise ValueError(
                "disabled_tools must use Codewhale catalog identifiers: "
                + ", ".join(repr(tool) for tool in invalid)
            )

    @property
    def binary(self) -> str:
        configured = (self.config.binary_path or "").strip()
        return configured or DEFAULT_BINARY

    async def setup(self, runtime: Runtime) -> None:
        self._validate_config()
        if self.config.binary_path:
            logger.info("codewhale: verifying preinstalled %s", self.binary)
            result = await runtime.run([self.binary, "--version"], {})
            version_text = f"{result.stdout}\n{result.stderr}"
            if result.exit_code != 0 or not _has_version(
                version_text, self.config.version
            ):

View on GitHub (pinned to 8880682c63)

Solutions

  1. Use exact catalog identifiers: lowercase letters, digits, underscores, hyphens
  2. List the tool catalog with the binary's tool-listing command and copy names verbatim
  3. Filter entries through the same regex before building the config

Example fix

# before
config = CodewhaleHarnessConfig(version='0.9.1', disabled_tools=['Bash', 'Read File'])
# after
config = CodewhaleHarnessConfig(version='0.9.1', disabled_tools=['bash', 'read_file'])
Defensive patterns

Strategy: type-guard

Validate before calling

import re
_TOOL = re.compile(r'^[a-z0-9][a-z0-9_-]*$')
tools = [t for t in configured_tools if _TOOL.fullmatch(t)]
config = CodewhaleHarnessConfig(version='0.9.1', disabled_tools=tools)

Type guard

import re
_TOOL = re.compile(r'^[a-z0-9][a-z0-9_-]*$')

def valid_tool_id(t):
    return isinstance(t, str) and _TOOL.fullmatch(t) is not None

Prevention

When it happens

Trigger: disabled_tools=['Bash'], ['read file'], ['-grep'], or dotted names like ['fs.read_file'].

Common situations: Copying display names from another agent CLI; mixing glob patterns or module-qualified ids into the list; case-changing config merges.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/4e2cb6269ef0330d. Report an issue: GitHub.