From f319fafdcf1483342c71b230d88fc11a5226a9c3 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Thu, 19 Mar 2026 13:58:28 +0100 Subject: [PATCH] fix: use %v instead of %s to format script tool args as env vars When script tool arguments have type: number, the LLM sends JSON numbers which are unmarshaled as float64 in Go. Formatting float64 with %s produces '%!s(float64=42)' instead of '42'. Using %v correctly formats all types. Fixes #2169 Assisted-By: docker-agent --- pkg/tools/builtin/script_shell.go | 2 +- pkg/tools/builtin/script_shell_test.go | 39 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/pkg/tools/builtin/script_shell.go b/pkg/tools/builtin/script_shell.go index 4753cd717..6c45fdf6d 100644 --- a/pkg/tools/builtin/script_shell.go +++ b/pkg/tools/builtin/script_shell.go @@ -144,7 +144,7 @@ func (t *ScriptShellTool) execute(ctx context.Context, toolConfig *latest.Script cmd.Env = t.env for key, value := range params { if value != nil { - cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, value)) + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%v", key, value)) } } diff --git a/pkg/tools/builtin/script_shell_test.go b/pkg/tools/builtin/script_shell_test.go index 7388b1ea9..dbb36eb9b 100644 --- a/pkg/tools/builtin/script_shell_test.go +++ b/pkg/tools/builtin/script_shell_test.go @@ -2,12 +2,14 @@ package builtin import ( "encoding/json" + "os" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/docker/docker-agent/pkg/config/latest" + "github.com/docker/docker-agent/pkg/tools" ) func TestNewScriptShellTool_Empty(t *testing.T) { @@ -116,6 +118,43 @@ func TestNewScriptShellTool_MissingRequired(t *testing.T) { require.ErrorContains(t, err, "tool 'docker_images' has required arg 'img' which is not defined in args") } +func TestNewScriptShellTool_NumberArg(t *testing.T) { + shellTools := map[string]latest.ScriptShellToolConfig{ + "repeat": { + Description: "Repeat a message N times", + Cmd: "for i in $(seq 1 $count); do echo $message; done", + Args: map[string]any{ + "message": map[string]any{ + "description": "Message to repeat", + "type": "string", + }, + "count": map[string]any{ + "description": "Number of repetitions", + "type": "number", + }, + }, + Required: []string{"message", "count"}, + }, + } + + tool, err := NewScriptShellTool(shellTools, os.Environ()) + require.NoError(t, err) + + allTools, err := tool.Tools(t.Context()) + require.NoError(t, err) + require.Len(t, allTools, 1) + + // Simulate LLM sending a number argument (JSON numbers are float64) + result, err := allTools[0].Handler(t.Context(), tools.ToolCall{ + Function: tools.FunctionCall{ + Arguments: `{"message": "hello", "count": 3}`, + }, + }) + require.NoError(t, err) + assert.False(t, result.IsError, "unexpected error: %s", result.Output) + assert.Equal(t, "hello\nhello\nhello\n", result.Output) +} + func TestNewScriptShellTool_ArgWithoutType(t *testing.T) { shellTools := map[string]latest.ScriptShellToolConfig{ "greet": {