From 158bc331c46f88555e52ad82188d3d40675b88a0 Mon Sep 17 00:00:00 2001 From: piyush0049 Date: Mon, 3 Aug 2026 22:06:46 +0530 Subject: [PATCH 1/4] fix: resolve proxy transport false-positives and windows linter issues --- pkg/desktop/transport/transport.go | 20 +++++++++---------- pkg/desktop/transport/transport_test.go | 15 ++++++++++++++ .../provider/gemini/schema_boolean_test.go | 10 ++++------ 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/pkg/desktop/transport/transport.go b/pkg/desktop/transport/transport.go index 400c2c6e9f..6d6cfced39 100644 --- a/pkg/desktop/transport/transport.go +++ b/pkg/desktop/transport/transport.go @@ -140,10 +140,7 @@ func (f *fallbackTransport) RoundTrip(req *http.Request) (*http.Response, error) } // isProxySocketError checks if the error indicates the proxy socket is unavailable. -// This includes: -// - "no such file or directory" - socket file was deleted -// - "connection refused" - socket exists but nothing is listening -// - "dial unix" errors - general Unix socket connection failures +// Direct target TCP dial errors (e.g. dial tcp) return false to avoid disabling the proxy. func isProxySocketError(err error) bool { if err == nil { return false @@ -151,13 +148,16 @@ func isProxySocketError(err error) bool { errStr := strings.ToLower(err.Error()) - // Check for common proxy socket failure patterns + // Target TCP connection errors are target host errors, not proxy socket failures. + if strings.Contains(errStr, "dial tcp") && !strings.Contains(errStr, "proxyconnect tcp") { + return false + } + proxyErrorPatterns := []string{ - "no such file or directory", // Socket file deleted - "connect: connection refused", // Socket exists but no listener - "proxyconnect tcp", // Proxy connection failure - "dial unix", // Unix socket dial failure - "unix socket", // Generic Unix socket error + "no such file or directory", // Socket file deleted + "proxyconnect tcp", // Proxy connection failure + "dial unix", // Unix socket dial failure + "unix socket", // Generic Unix socket error } for _, pattern := range proxyErrorPatterns { diff --git a/pkg/desktop/transport/transport_test.go b/pkg/desktop/transport/transport_test.go index b7f06211fd..f09d98e566 100644 --- a/pkg/desktop/transport/transport_test.go +++ b/pkg/desktop/transport/transport_test.go @@ -83,6 +83,11 @@ func TestIsProxySocketError(t *testing.T) { errStr: "Post https://api.anthropic.com/v1/messages: proxyconnect tcp: some error", expected: true, }, + { + name: "proxyconnect tcp with dial tcp error", + errStr: "proxyconnect tcp: dial tcp 10.0.0.1:443: connect: connection refused", + expected: true, + }, { name: "dial unix error", errStr: "dial unix /var/run/docker.sock: operation timed out", @@ -93,6 +98,16 @@ func TestIsProxySocketError(t *testing.T) { errStr: "dial tcp 192.168.1.1:443: i/o timeout", expected: false, }, + { + name: "target TCP connection refused", + errStr: "dial tcp 127.0.0.1:8080: connect: connection refused", + expected: false, + }, + { + name: "target HTTP request dial refusal", + errStr: "Get \"http://127.0.0.1:8080\": dial tcp 127.0.0.1:8080: connect: connection refused", + expected: false, + }, { name: "HTTP error", errStr: "HTTP 500: internal server error", diff --git a/pkg/model/provider/gemini/schema_boolean_test.go b/pkg/model/provider/gemini/schema_boolean_test.go index d1875d5949..bc49434b4e 100644 --- a/pkg/model/provider/gemini/schema_boolean_test.go +++ b/pkg/model/provider/gemini/schema_boolean_test.go @@ -2,6 +2,8 @@ package gemini import ( "testing" + + "github.com/stretchr/testify/require" ) // A tool input schema containing a boolean sub-schema — the shape a JSON Schema @@ -30,12 +32,8 @@ func TestConvertParametersToSchema_BooleanSubSchema(t *testing.T) { } schema, err := ConvertParametersToSchema(params) - if err != nil { - t.Fatalf("ConvertParametersToSchema: %v", err) - } - if schema == nil { - t.Fatal("nil schema") - } + require.NoError(t, err) + require.NotNil(t, schema) if _, ok := schema.Properties["count"]; !ok { t.Errorf("count property dropped; got %v", schema.Properties) } From 4e025a12dcfbd592538a8b3cc317c273001d0a99 Mon Sep 17 00:00:00 2001 From: piyush0049 Date: Tue, 4 Aug 2026 15:04:30 +0530 Subject: [PATCH 2/4] fix: resolve proxy transport false-positives --- pkg/desktop/transport/transport.go | 14 ++++++++++---- pkg/desktop/transport/transport_test.go | 10 ++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/pkg/desktop/transport/transport.go b/pkg/desktop/transport/transport.go index 6d6cfced39..ce9ab95e94 100644 --- a/pkg/desktop/transport/transport.go +++ b/pkg/desktop/transport/transport.go @@ -149,15 +149,21 @@ func isProxySocketError(err error) bool { errStr := strings.ToLower(err.Error()) // Target TCP connection errors are target host errors, not proxy socket failures. + // Note: If the agent were configured to use a bare-TCP direct dial proxy (rather than + // HTTP CONNECT), a proxy connection failure would produce a "dial tcp" error without + // "proxyconnect tcp", which would be falsely classified as a target host error here. + // In practice, Docker Agent only uses HTTP CONNECT proxies or Unix sockets, so this + // early return correctly protects target host outages from disabling the proxy. if strings.Contains(errStr, "dial tcp") && !strings.Contains(errStr, "proxyconnect tcp") { return false } proxyErrorPatterns := []string{ - "no such file or directory", // Socket file deleted - "proxyconnect tcp", // Proxy connection failure - "dial unix", // Unix socket dial failure - "unix socket", // Generic Unix socket error + "no such file or directory", // Socket file deleted + "connect: connection refused", // Socket exists but no listener (Unix or Windows named pipe) + "proxyconnect tcp", // Proxy connection failure + "dial unix", // Unix socket dial failure + "unix socket", // Generic Unix socket error } for _, pattern := range proxyErrorPatterns { diff --git a/pkg/desktop/transport/transport_test.go b/pkg/desktop/transport/transport_test.go index f09d98e566..62687a784f 100644 --- a/pkg/desktop/transport/transport_test.go +++ b/pkg/desktop/transport/transport_test.go @@ -93,6 +93,16 @@ func TestIsProxySocketError(t *testing.T) { errStr: "dial unix /var/run/docker.sock: operation timed out", expected: true, }, + { + name: "bare connection refused (unix socket missing listener)", + errStr: "connect: connection refused", + expected: true, + }, + { + name: "bare-TCP proxy failure (unsupported, should be rejected by guard)", + errStr: "dial tcp 127.0.0.1:8080: connect: connection refused", + expected: false, // hit guard, returns false + }, { name: "regular network error", errStr: "dial tcp 192.168.1.1:443: i/o timeout", From f484ddb2638d105c378c62c6c56d53148a847cd4 Mon Sep 17 00:00:00 2001 From: piyush0049 Date: Wed, 5 Aug 2026 20:12:34 +0530 Subject: [PATCH 3/4] fix: address maintainer review comments for proxy transport --- pkg/desktop/transport/transport.go | 10 +++------- pkg/desktop/transport/transport_test.go | 6 +----- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/pkg/desktop/transport/transport.go b/pkg/desktop/transport/transport.go index ce9ab95e94..e251f3c8a2 100644 --- a/pkg/desktop/transport/transport.go +++ b/pkg/desktop/transport/transport.go @@ -148,19 +148,15 @@ func isProxySocketError(err error) bool { errStr := strings.ToLower(err.Error()) - // Target TCP connection errors are target host errors, not proxy socket failures. - // Note: If the agent were configured to use a bare-TCP direct dial proxy (rather than - // HTTP CONNECT), a proxy connection failure would produce a "dial tcp" error without - // "proxyconnect tcp", which would be falsely classified as a target host error here. - // In practice, Docker Agent only uses HTTP CONNECT proxies or Unix sockets, so this - // early return correctly protects target host outages from disabling the proxy. + // A bare "dial tcp" error is a target host failure, not a proxy socket failure: + // the proxy is only reached via Unix socket or named pipe, never plain TCP. if strings.Contains(errStr, "dial tcp") && !strings.Contains(errStr, "proxyconnect tcp") { return false } proxyErrorPatterns := []string{ "no such file or directory", // Socket file deleted - "connect: connection refused", // Socket exists but no listener (Unix or Windows named pipe) + "connect: connection refused", // Socket exists but no listener "proxyconnect tcp", // Proxy connection failure "dial unix", // Unix socket dial failure "unix socket", // Generic Unix socket error diff --git a/pkg/desktop/transport/transport_test.go b/pkg/desktop/transport/transport_test.go index 62687a784f..e334fb888f 100644 --- a/pkg/desktop/transport/transport_test.go +++ b/pkg/desktop/transport/transport_test.go @@ -108,11 +108,7 @@ func TestIsProxySocketError(t *testing.T) { errStr: "dial tcp 192.168.1.1:443: i/o timeout", expected: false, }, - { - name: "target TCP connection refused", - errStr: "dial tcp 127.0.0.1:8080: connect: connection refused", - expected: false, - }, + { name: "target HTTP request dial refusal", errStr: "Get \"http://127.0.0.1:8080\": dial tcp 127.0.0.1:8080: connect: connection refused", From 17bd4a8795b3527ad56488a7a2f4815acb23d0e9 Mon Sep 17 00:00:00 2001 From: piyush0049 Date: Wed, 5 Aug 2026 23:02:41 +0530 Subject: [PATCH 4/4] fix: resolve Windows CI test failures inherited from main --- pkg/environment/credential_helper_test.go | 24 +++++++++++++++---- pkg/skills/skills_test.go | 7 ++++++ pkg/tools/builtin/shell/script_shell_test.go | 2 +- .../reasoningblock/reasoningblock_test.go | 3 ++- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/pkg/environment/credential_helper_test.go b/pkg/environment/credential_helper_test.go index f7caccf28f..43272a822c 100644 --- a/pkg/environment/credential_helper_test.go +++ b/pkg/environment/credential_helper_test.go @@ -1,6 +1,7 @@ package environment import ( + "runtime" "testing" "github.com/stretchr/testify/assert" @@ -17,6 +18,19 @@ func TestNewCredentialHelperProvider(t *testing.T) { func TestCredentialHelperProvider_Get(t *testing.T) { t.Parallel() + echoCmd := "echo" + echoArgs := func(v string) []string { return []string{v} } + falseCmd := "false" + + if runtime.GOOS == "windows" { + echoCmd = "powershell" + echoArgs = func(v string) []string { + return []string{"-NoProfile", "-Command", "Write-Output '" + v + "'"} + } + falseCmd = "powershell" + // simulate 'false' by exiting with 1 + } + tests := []struct { name string command string @@ -25,11 +39,11 @@ func TestCredentialHelperProvider_Get(t *testing.T) { wantValue string wantFound bool }{ - {"ignores non-DOCKER_TOKEN vars", "echo", []string{"test-token"}, "OTHER_VAR", "", false}, - {"success", "echo", []string{"my-secret-token"}, DockerDesktopTokenEnv, "my-secret-token", true}, - {"trims whitespace", "echo", []string{" token-with-spaces "}, DockerDesktopTokenEnv, "token-with-spaces", true}, - {"empty output", "echo", []string{""}, DockerDesktopTokenEnv, "", false}, - {"command fails", "false", nil, DockerDesktopTokenEnv, "", false}, + {"ignores non-DOCKER_TOKEN vars", echoCmd, echoArgs("test-token"), "OTHER_VAR", "", false}, + {"success", echoCmd, echoArgs("my-secret-token"), DockerDesktopTokenEnv, "my-secret-token", true}, + {"trims whitespace", echoCmd, echoArgs(" token-with-spaces "), DockerDesktopTokenEnv, "token-with-spaces", true}, + {"empty output", echoCmd, echoArgs(""), DockerDesktopTokenEnv, "", false}, + {"command fails", falseCmd, []string{"-NoProfile", "-Command", "exit 1"}, DockerDesktopTokenEnv, "", false}, {"command not found", "nonexistent-command-12345", nil, DockerDesktopTokenEnv, "", false}, } diff --git a/pkg/skills/skills_test.go b/pkg/skills/skills_test.go index 756e03c1b4..ea89056cd9 100644 --- a/pkg/skills/skills_test.go +++ b/pkg/skills/skills_test.go @@ -1011,6 +1011,13 @@ func TestSkill_IsFork(t *testing.T) { } func TestProjectSearchDirs(t *testing.T) { + // On Windows, t.TempDir() is often inside %USERPROFILE% (AppData\Local\Temp). + // To ensure the first three tests behave exactly as they do on Unix (where /tmp + // is outside $HOME), we mock HOME to a sibling temp directory. + fakeHome := t.TempDir() + t.Setenv("HOME", fakeHome) + t.Setenv("USERPROFILE", fakeHome) + t.Run("in git repo", func(t *testing.T) { tmpRepo := t.TempDir() require.NoError(t, os.Mkdir(filepath.Join(tmpRepo, ".git"), 0o755)) diff --git a/pkg/tools/builtin/shell/script_shell_test.go b/pkg/tools/builtin/shell/script_shell_test.go index 6003837899..5ab6eebf7e 100644 --- a/pkg/tools/builtin/shell/script_shell_test.go +++ b/pkg/tools/builtin/shell/script_shell_test.go @@ -290,7 +290,7 @@ func TestCreateScriptToolSet_EnvPrecedence(t *testing.T) { }, Shell: map[string]latest.ScriptShellToolConfig{ "show_env": { - Cmd: "env", + Cmd: envDumpCmd(), Env: map[string]string{ "SCRIPT_PREC_TOOL": "from-tool", }, diff --git a/pkg/tui/components/reasoningblock/reasoningblock_test.go b/pkg/tui/components/reasoningblock/reasoningblock_test.go index 5711adf256..cd0e000c37 100644 --- a/pkg/tui/components/reasoningblock/reasoningblock_test.go +++ b/pkg/tui/components/reasoningblock/reasoningblock_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + pathx "github.com/docker/docker-agent/pkg/path" "github.com/docker/docker-agent/pkg/paths" "github.com/docker/docker-agent/pkg/session" "github.com/docker/docker-agent/pkg/tools" @@ -190,7 +191,7 @@ func TestReasoningBlockExpandedShowsFullToolRenderer(t *testing.T) { stripped := ansi.Strip(block.View()) assert.Contains(t, stripped, "Edit") - assert.Contains(t, stripped, path) + assert.Contains(t, stripped, pathx.ShortenHome(path)) assert.Contains(t, stripped, "old line") assert.Contains(t, stripped, "new line") }