{"record":{"id":"912a13a23a6e87de","repo":"can1357/oh-my-pi","slug":"tool-self-name-expects-a-dict-of-arguments","errorCode":null,"errorMessage":"tool.{self._name}(...) expects a dict of arguments (got {type(args).__name__})","messagePattern":"tool\\.(.+?)\\(\\.\\.\\.\\) expects a dict of arguments \\(got (.+?)\\)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/eval/py/prelude.py","lineNumber":439,"sourceCode":"\n    class _ToolCallable:\n        \"\"\"Invokes one host-side tool via the loopback HTTP bridge.\"\"\"\n\n        __slots__ = (\"_name\",)\n\n        def __init__(self, name: str):\n            self._name = name\n\n        def __repr__(self) -> str:\n            return f\"<tool.{self._name}>\"\n\n        def __call__(self, args=None, /, **kwargs):\n            if args is None:\n                merged: dict = {}\n            elif isinstance(args, dict):\n                merged = dict(args)\n            else:\n                raise TypeError(\n                    f\"tool.{self._name}(...) expects a dict of arguments (got {type(args).__name__})\"\n                )\n            merged.update(kwargs)\n            if INTENT_FIELD not in merged:\n                merged[INTENT_FIELD] = \"py prelude\"\n            return _bridge_call(self._name, merged)\n\n    class _ToolProxy:\n        \"\"\"`tool.<name>(args)` proxy mirroring the JS runtime bridge.\"\"\"\n\n        __slots__ = ()\n\n        def __getattr__(self, name: str) -> _ToolCallable:\n            if name.startswith(\"_\"):\n                raise AttributeError(name)\n            return _ToolCallable(name)\n\n        def __getitem__(self, name: str) -> _ToolCallable:","sourceCodeStart":421,"sourceCodeEnd":457,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/eval/py/prelude.py#L421-L457","documentation":"`_ToolCallable.__call__` merges an optional positional argument with keyword arguments and requires the result to be a dict, since tool arguments travel as a JSON object over the bridge. This TypeError is raised when the positional argument is not a dict (e.g. a list, string, or kwargs-style pile of non-dict values).","triggerScenarios":"Calling `tool.bash(\"ls\")` instead of `tool.bash({\"command\": \"ls\"})`; passing a list like `tool.read_file([\"a.txt\"])`; positional non-dict values of any type.","commonSituations":"Porting JS-style calls where args were JSON strings; confusing Python kwargs with the dict form (`tool.bash(command=\"ls\")` is fine via **kwargs, but `tool.bash(\"ls\")` is not); writing helpers that pass through arbitrary user data.","solutions":["Wrap arguments in a dict keyed by the tool's parameter names: `tool.bash({\"command\": \"ls\"})`.","Or use keyword arguments directly: `tool.bash(command=\"ls\")`.","If args come from a variable, ensure it is a dict before calling (`isinstance(args, dict)`)."],"exampleFix":"// before\nresult = tool.bash(\"ls -la\")\n\n// after\nresult = tool.bash({\"command\": \"ls -la\"})\n# or equivalently\nresult = tool.bash(command=\"ls -la\")","handlingStrategy":"type-guard","validationCode":"args = {\"command\": cmd}\nif not isinstance(args, dict):\n    raise SystemExit(\"tool args must be a dict\")\nresult = tool.bash(args)","typeGuard":"def is_tool_args(args) -> bool:\n    return isinstance(args, dict)","tryCatchPattern":"try:\n    result = tool.bash(cmd)\nexcept TypeError as e:\n    if \"expects a dict of arguments\" in str(e):\n        result = tool.bash({\"command\": cmd})","preventionTips":["Always pass tool arguments as a dict: tool.name({\"key\": value}).","Prefer keyword form (tool.name(command=\"ls\")) which sidesteps the positional trap.","Don't pass JSON strings, lists, or bare values positionally — only dicts merge with kwargs."],"tags":["python","typeerror","api-misuse","eval"],"backgroundTag":"invalid-argument-type","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}