{"record":{"id":"8491b5dea1659a12","repo":"can1357/oh-my-pi","slug":"name","errorCode":null,"errorMessage":"name","messagePattern":"name","errorType":"exception","errorClass":"AttributeError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/eval/py/prelude.py","lineNumber":454,"sourceCode":"            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:\n            return _ToolCallable(name)\n\n        def __repr__(self) -> str:\n            session = os.environ.get(\"PI_TOOL_BRIDGE_SESSION\")\n            return (\n                f\"<tool proxy session={session}>\"\n                if session\n                else \"<tool proxy unavailable>\"\n            )\n\n    tool = _ToolProxy()\n\n    def completion(prompt, *, model=\"default\", system=None, schema=None):\n        \"\"\"Oneshot, stateless completion against a model tier.\n","sourceCodeStart":436,"sourceCodeEnd":472,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/eval/py/prelude.py#L436-L472","documentation":"The `tool` proxy raises AttributeError for any attribute access starting with `_`, reserving underscore-prefixed names for Python internals and preventing them from being mistaken for tool calls. Accessing `tool._something` (or Python's own dunder probing like `tool.__deepcopy__`) hits this path instead of creating a `_ToolCallable`.","triggerScenarios":"Accessing `tool._internal`, calling `hasattr(tool, \"_x\")`, or any protocol probe that looks up `__<name>__` / `_<name>` attributes on the proxy.","commonSituations":"Accidentally treating `tool` as a module and reaching for private helpers; serialization/copy frameworks probing dunder methods (`__getstate__`, `_repr_html_`); an IDE autocompletion exploring underscore names.","solutions":["Drop the leading underscore and call the tool by its real public name: `tool.read_file(...)`.","If you need a tool whose name genuinely starts with `_`, use the item form which bypasses the guard: `tool[\"_name\"](...)`.","Don't try to access Python-internal attributes on the proxy; it intentionally exposes only tool-callable names."],"exampleFix":"// before\nprivate = tool._debug_state()  # AttributeError\n\n// after\nif tool_name.startswith(\"_\"):\n    private = tool[tool_name]()   # item access bypasses the underscore guard\nelse:\n    private = getattr(tool, tool_name)()","handlingStrategy":"try-catch","validationCode":"name = \"_debug\"\nif name.startswith(\"_\"):\n    print(\"underscore names are reserved; use tool[name] item access for such tools\")","typeGuard":null,"tryCatchPattern":"try:\n    callable_ = getattr(tool, name)\nexcept AttributeError:\n    if name.startswith(\"_\"):\n        callable_ = tool[name]  # item access bypasses the underscore guard\n    else:\n        raise","preventionTips":["Treat `tool` as a call-by-public-name proxy only; don't reach for private attributes on it.","For tools whose names start with underscore, use the subscript form tool[\"_name\"](...).","Expect framework dunder probes (`__deepcopy__`, `_repr_html_`) to raise; register the proxy with copy/serialization hooks explicitly if needed."],"tags":["python","attributeerror","api-misuse","proxy"],"backgroundTag":"attribute-error","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}