From 1aea013e20da4db3697b0a0ae6803d61829b4ab5 Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Wed, 22 Apr 2026 13:18:46 -0700 Subject: [PATCH] =?UTF-8?q?fix(ci):=20unblock=20main=20CI=20on=20ubuntu-la?= =?UTF-8?q?test=20=E2=80=94=20IPv6-safe=20addr=20+=20MagicMock=20seed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two latent bugs the self-hosted Mac mini had been hiding. Both caught by the newer toolchain on ubuntu-latest runners after PR #1626. 1. workspace-server/internal/handlers/terminal.go:442 `fmt.Sprintf("%s:%d", host, port)` flagged by go vet as unsafe for IPv6 (it omits the required [::] brackets). Replaced with `net.JoinHostPort(host, strconv.Itoa(port))` which handles both IPv4 and IPv6 correctly. No runtime behaviour change — the only call site passes "127.0.0.1", so the bug would never trigger in practice, but vet is right to flag it as a latent correctness issue. 2. workspace/tests/test_a2a_executor.py::test_set_current_task_updates_heartbeat `MagicMock()` auto-creates attributes on first access, so `getattr(heartbeat, "active_tasks", 0)` in shared_runtime.py returned a MagicMock rather than the default 0. Adding 1 to a MagicMock returns another MagicMock, so the assertion `heartbeat.active_tasks == 1` never held. Seeding `heartbeat.active_tasks = 0` before the first call makes getattr() return a real int, matching how the real HeartbeatLoop class initialises itself. Both pre-existed on main and were hidden by the older Python / Go toolchains on the Mac mini runner. Verified locally (venv pytest pass, `go vet ./...` + `go build ./...` clean on workspace-server). Co-Authored-By: Claude Opus 4.7 (1M context) --- workspace-server/internal/handlers/terminal.go | 5 ++++- workspace/tests/test_a2a_executor.py | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/workspace-server/internal/handlers/terminal.go b/workspace-server/internal/handlers/terminal.go index 18b1b4cc..94e81cd6 100644 --- a/workspace-server/internal/handlers/terminal.go +++ b/workspace-server/internal/handlers/terminal.go @@ -9,6 +9,7 @@ import ( "net/http" "os" "os/exec" + "strconv" "strings" "time" @@ -439,7 +440,9 @@ func pickFreePort() (int, error) { // its local port before we dial ssh at it. func waitForPort(ctx context.Context, host string, port int, timeout time.Duration) error { deadline := time.Now().Add(timeout) - addr := fmt.Sprintf("%s:%d", host, port) + // JoinHostPort handles IPv6 bracketing; `%s:%d` does not. Caught by + // `go vet` on ubuntu-latest (newer Go toolchain than the Mac mini). + addr := net.JoinHostPort(host, strconv.Itoa(port)) for time.Now().Before(deadline) { if ctx.Err() != nil { return ctx.Err() diff --git a/workspace/tests/test_a2a_executor.py b/workspace/tests/test_a2a_executor.py index 9194cd96..f393dfad 100644 --- a/workspace/tests/test_a2a_executor.py +++ b/workspace/tests/test_a2a_executor.py @@ -408,7 +408,13 @@ def test_extract_history_non_list(): @pytest.mark.asyncio async def test_set_current_task_updates_heartbeat(): """set_current_task updates heartbeat fields.""" + # Seed active_tasks as an int — without this, MagicMock auto-creates + # the attribute on first access, getattr() returns a MagicMock, and + # `MagicMock + 1` stays a MagicMock instead of becoming 1. The real + # HeartbeatLoop class initialises active_tasks=0 so this matches + # production behaviour. heartbeat = MagicMock() + heartbeat.active_tasks = 0 await set_current_task(heartbeat, "Doing work") assert heartbeat.current_task == "Doing work" assert heartbeat.active_tasks == 1