From 1d2a3499c9a1b048ec4a3eb78341c70174388111 Mon Sep 17 00:00:00 2001 From: Bohan Jiang <52446949+Bohan-J@users.noreply.github.com> Date: Thu, 30 Jul 2026 16:15:36 +0800 Subject: [PATCH] test(agent): stop the cursor fixtures racing their own prompt write (MUL-5536) (#6174) TestCursorExecuteFailsOnCleanEOFWithoutResult failed on main with "cursor-agent prompt write failed: write |1: broken pipe" where it expects "stream ended without terminal result". The fake cursor-agent exits without reading stdin, so the prompt write races the child's exit: win and the pipe buffer swallows it, lose and the read end is gone and the write returns EPIPE. writeErr outranks both exitErr and the generic no-terminal-result error in cursor.go, so a lost race replaces the asserted failure with the EPIPE one. A real cursor-agent reads stdin to EOF, so the fixtures now do too. That removes the race rather than reordering production error precedence, which is deliberate. Only the two fixtures whose expected error ranks below writeErr need the drain. Co-authored-by: Bohan-J Co-authored-by: multica-agent --- server/pkg/agent/cursor_execute_unix_test.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/server/pkg/agent/cursor_execute_unix_test.go b/server/pkg/agent/cursor_execute_unix_test.go index bc9b862b53..61e5589eb0 100644 --- a/server/pkg/agent/cursor_execute_unix_test.go +++ b/server/pkg/agent/cursor_execute_unix_test.go @@ -11,6 +11,23 @@ import ( "time" ) +// A real cursor-agent reads the prompt from stdin to EOF (see buildCursorArgs). +// A fake that exits without reading it leaves the prompt write racing the +// child's exit: win and the ~64 KiB pipe buffer swallows it, lose and the read +// end is already closed and the write fails with EPIPE. +// +// That matters because writeErr outranks both `exitErr` and the generic +// "stream ended without terminal result" when finalizing the error (cursor.go), +// so a lost race replaces the failure the test is asserting on with +// "cursor-agent prompt write failed: broken pipe" — the flake that turned main +// red on 2026-07-30 (MUL-5536). +// +// Draining stdin first makes the fake honour the same contract as the real CLI, +// which removes the race instead of papering over it. Only fixtures whose +// expected error ranks below writeErr need it; the scanner-overflow and +// structured-stream-error cases rank above it and are unaffected. +const drainStdin = "cat > /dev/null" + func TestCursorExecuteStopsAfterTerminalResult(t *testing.T) { t.Parallel() @@ -108,6 +125,7 @@ func TestCursorExecuteReportsSanitizedStderrOnProcessFailure(t *testing.T) { } script := `#!/bin/sh +` + drainStdin + ` dd if=/dev/zero bs=4096 count=1 2>/dev/null | tr '\000' x >&2 printf '\nAuthorization: Bearer cursor-secret-token-value\npath=%s/private\n' "$HOME" >&2 exit 1 @@ -196,6 +214,7 @@ printf '\n' func TestCursorExecuteFailsOnCleanEOFWithoutResult(t *testing.T) { script := `#!/bin/sh +` + drainStdin + ` printf '%s\n' '{"type":"system","subtype":"init","session_id":"sess-no-result"}' printf '%s\n' '{"type":"assistant","message":{"content":[{"type":"text","text":"partial answer"}]}}' `