{"record":{"id":"21d295f4a1d366e6","repo":"NousResearch/hermes-agent","slug":"tool-args-must-be-a-mapping-got-type-args-nam","errorCode":null,"errorMessage":"tool args must be a mapping, got {type(args).__name__}","messagePattern":"tool args must be a mapping, got (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"agent/tool_guardrails.py","lineNumber":228,"sourceCode":"        return self.action in {\"block\", \"halt\"}\n\n    def to_metadata(self) -> dict[str, Any]:\n        data: dict[str, Any] = {\n            \"action\": self.action,\n            \"code\": self.code,\n            \"message\": self.message,\n            \"tool_name\": self.tool_name,\n            \"count\": self.count,\n        }\n        if self.signature is not None:\n            data[\"signature\"] = self.signature.to_metadata()\n        return data\n\n\ndef canonical_tool_args(args: Mapping[str, Any]) -> str:\n    \"\"\"Return sorted compact JSON for parsed tool arguments.\"\"\"\n    if not isinstance(args, Mapping):\n        raise TypeError(f\"tool args must be a mapping, got {type(args).__name__}\")\n    return json.dumps(\n        args,\n        ensure_ascii=False,\n        sort_keys=True,\n        separators=(\",\", \":\"),\n        default=str,\n    )\n\n\ndef classify_tool_failure(tool_name: str, result: str | None) -> tuple[bool, str]:\n    \"\"\"Safety-fallback classifier used only when callers don't pass ``failed``.\n\n    Mirrors ``agent.display._detect_tool_failure`` exactly so the guardrail\n    never disagrees with the CLI's user-visible ``[error]`` tag. Production\n    callers in ``run_agent.py`` always pass an explicit ``failed=`` derived\n    from ``_detect_tool_failure``; this function exists so standalone callers\n    (tests, tooling) still get consistent behavior.\n    \"\"\"","sourceCodeStart":210,"sourceCodeEnd":246,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/agent/tool_guardrails.py#L210-L246","documentation":"Raised by canonical_tool_args() in agent/tool_guardrails.py when its args parameter is not a Mapping (dict-like). The function builds a deterministic, sorted, compact JSON representation of tool arguments for signature/failure tracking; lists, strings, bytes, or None are not valid because tool arguments are named parameters.","triggerScenarios":"Passing a JSON string of arguments (from an LLM tool_call before parsing) instead of the parsed dict; passing a positional list of args; passing None when a tool has no arguments (use {} instead); a caller passing the whole tool_call object rather than its .args field.","commonSituations":"Integrating raw provider payloads where function arguments arrive as a JSON string; forwarding args from a schema-less source; default-None spreads like canonical_tool_args(args or []) picking a list.","solutions":["Parse before canonicalizing: json.loads(raw_args) if isinstance(raw_args, str) — then pass the resulting dict.","Use {} (not None or []) for no-argument tools.","Type-check at the boundary: raise a clear error if args is not a Mapping before calling library code."],"exampleFix":"# before\ncanonical = canonical_tool_args(tool_call.function.arguments)  # str from provider\n\n# after\nimport json\nraw = tool_call.function.arguments\nargs = json.loads(raw) if isinstance(raw, str) else (raw or {})\ncanonical = canonical_tool_args(args)","handlingStrategy":"type-guard","validationCode":"import json\nfrom typing import Mapping\n\nraw = tool_call.get(\"function\", {}).get(\"arguments\", {})\nargs = json.loads(raw) if isinstance(raw, str) else (raw if isinstance(raw, Mapping) else {})","typeGuard":"from typing import Mapping\n\ndef is_tool_args_mapping(value: object) -> bool:\n    return isinstance(value, Mapping)","tryCatchPattern":"try:\n    canonical = canonical_tool_args(args)\nexcept TypeError as exc:\n    if \"tool args must be a mapping\" in str(exc):\n        args = json.loads(args) if isinstance(args, str) else {}\n        canonical = canonical_tool_args(args)\n    else:\n        raise","preventionTips":["Always json.loads() provider argument strings before passing them on.","Use {} for tools invoked with no arguments, never None or [].","Pass the parsed .args dict, not the enclosing tool_call object."],"tags":["tools","guardrails","validation","json"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}