{"record":{"id":"9ebee1f522142c4b","repo":"can1357/oh-my-pi","slug":"tool-name-expects-a-hash-of-arguments-got","errorCode":null,"errorMessage":"tool.#{name}(...) expects a Hash of arguments (got #{positional.class})","messagePattern":"tool\\.#(.+?)\\(\\.\\.\\.\\) expects a Hash of arguments \\(got #(.+?)\\)","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/eval/rb/prelude.rb","lineNumber":334,"sourceCode":"        raise((data.is_a?(Hash) ? data[\"error\"] : nil) || \"bridge call #{name.inspect} failed\")\n      end\n      data[\"value\"]\n    end\n\n    def stringify_keys(hash)\n      out = {}\n      hash.each { |k, v| out[k.to_s] = v }\n      out\n    end\n\n    def tool_call(name, positional, kwargs)\n      merged =\n        if positional.nil?\n          {}\n        elsif positional.is_a?(Hash)\n          stringify_keys(positional)\n        else\n          raise ArgumentError, \"tool.#{name}(...) expects a Hash of arguments (got #{positional.class})\"\n        end\n      merged.merge!(stringify_keys(kwargs)) if kwargs && !kwargs.empty?\n      merged[INTENT_FIELD] = \"rb prelude\" unless merged.key?(INTENT_FIELD)\n      call(name, merged)\n    end\n  end\n\n  # `tool[:name]` form — a reusable one-tool callable.\n  class OmpToolCallable\n    def initialize(name)\n      @name = name\n    end\n\n    def call(args = nil, **kwargs)\n      OmpBridge.tool_call(@name, args, kwargs)\n    end\n\n    def to_proc","sourceCodeStart":316,"sourceCodeEnd":352,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/eval/rb/prelude.rb#L316-L352","documentation":"The `tool.<name>(...)` bridge wrapper accepts its arguments either as a single positional Hash or as Ruby keyword arguments, which are then merged and forwarded as the tool's JSON argument object. If a non-Hash positional argument is passed (String, Array, nil-adjacent scalars other than nil), the wrapper raises ArgumentError because tool arguments must serialize to a JSON object.","triggerScenarios":"Calling `tool.read_file('path/to/file')` with a bare string, `tool.search(['a','b'])` with an array, or passing a non-nil scalar as the only positional argument instead of `{ 'path' => ... }` or keyword form.","commonSituations":"Assuming positional strings map to a conventional first parameter name; converting Python-style `tool(name, args)` calls to Ruby incorrectly; passing a JSON string directly instead of a parsed Hash.","solutions":["Wrap the arguments in a Hash: `tool.read_file(path: 'x.txt')` or `tool.read_file('path' => 'x.txt')`.","If you have a JSON string, `JSON.parse(it)` it into a Hash first.","Check the tool's expected schema (via the host tool list) and supply every argument as hash keys."],"exampleFix":"// before\ntool.read_file('/etc/hosts')           # ArgumentError: got String\n// after\ntool.read_file(path: '/etc/hosts')     # or { 'path' => '/etc/hosts' }","handlingStrategy":"type-guard","validationCode":"raise ArgumentError, 'args must be a Hash' unless args.nil? || args.is_a?(Hash)","typeGuard":"def hash_args?(args)\n  args.nil? || args.is_a?(Hash)\nend","tryCatchPattern":"begin\n  tool.read_file(raw_arg)\nrescue ArgumentError => e\n  raise unless e.message =~ /expects a Hash/\n  tool.read_file('path' => raw_arg)\nend","preventionTips":["Always build tool arguments as Hashes (or keyword args) — never pass positional scalars.","JSON.parse any JSON strings into Hashes before passing to tool.<name>.","Check the tool schema for required parameter names before invoking."],"tags":["ruby","argument-error","api-misuse","tool-bridge"],"backgroundTag":"invalid-argument-type","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}