From 4f7989d79652aaad6269c2cf72cc9d641542b8ad Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 21 Mar 2026 11:04:18 +0800 Subject: [PATCH] feat(tests): enhance bash script validation in platform tests - Added assertions to ensure the presence and order of `set -e` and `set +e` in the generated bash script when a system prompt is included. - Updated the test for the scenario without a prompt to confirm that `set -e` is not present, ensuring correct script behavior. - Improved documentation in the `buildBashScript` function to clarify the use of `set -e` for error handling during script execution. --- agent/sandbox/v2/claude/platform.go | 7 +++++++ agent/sandbox/v2/claude/platform_test.go | 11 +++++++++++ agent/sandbox/v2/claude/session.go | 22 +++++++++++++++++++++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/agent/sandbox/v2/claude/platform.go b/agent/sandbox/v2/claude/platform.go index d2b60db9..c9a95a0f 100644 --- a/agent/sandbox/v2/claude/platform.go +++ b/agent/sandbox/v2/claude/platform.go @@ -68,6 +68,11 @@ func (b *posixBase) ListDirCmd(dir string) []string { } // buildBashScript is the shared bash script builder for macOS/Linux. +// +// When a system prompt is present, the script uses `set -e` to ensure that +// any failure in directory creation or prompt file writing aborts the entire +// script before Claude CLI is launched. This prevents silent fallback to +// running without a system prompt. func (b *posixBase) buildBashScript(in scriptInput, xauthCmd string) string { var s strings.Builder @@ -76,10 +81,12 @@ func (b *posixBase) buildBashScript(in scriptInput, xauthCmd string) string { } if in.systemPrompt != "" { + s.WriteString("set -e\n") s.WriteString(fmt.Sprintf("mkdir -p \"$(dirname %q)\"\n", in.promptFile)) s.WriteString(fmt.Sprintf("cat << 'PROMPTEOF' > %s\n", in.promptFile)) s.WriteString(in.systemPrompt) s.WriteString("\nPROMPTEOF\n") + s.WriteString("set +e\n") in.args = append(in.args, "--append-system-prompt-file", in.promptFile) } diff --git a/agent/sandbox/v2/claude/platform_test.go b/agent/sandbox/v2/claude/platform_test.go index f0b34253..71f4c453 100644 --- a/agent/sandbox/v2/claude/platform_test.go +++ b/agent/sandbox/v2/claude/platform_test.go @@ -82,6 +82,7 @@ func TestPosixBase_BuildBashScript_NoPrompt(t *testing.T) { assert.Contains(t, script, "--verbose") assert.Contains(t, script, "INPUTEOF") assert.NotContains(t, script, "PROMPTEOF") + assert.NotContains(t, script, "set -e", "set -e should not be present when no prompt is written") } func TestPosixBase_BuildBashScript_WithPrompt(t *testing.T) { @@ -98,6 +99,16 @@ func TestPosixBase_BuildBashScript_WithPrompt(t *testing.T) { assert.Contains(t, script, "PROMPTEOF") assert.Contains(t, script, "You are a helpful assistant.") assert.Contains(t, script, "--append-system-prompt-file") + + promptIdx := strings.Index(script, "PROMPTEOF") + claudeIdx := strings.Index(script, "claude -p") + assert.True(t, strings.Contains(script, "set -e"), "script should enable set -e before prompt write") + assert.True(t, strings.Contains(script, "set +e"), "script should disable set -e before claude command") + setEIdx := strings.Index(script, "set -e") + setNoEIdx := strings.Index(script, "set +e") + assert.Less(t, setEIdx, promptIdx, "set -e should come before PROMPTEOF") + assert.Less(t, promptIdx, setNoEIdx, "set +e should come after PROMPTEOF") + assert.Less(t, setNoEIdx, claudeIdx, "set +e should come before claude -p") } func TestPosixBase_BuildBashScript_WithXauth(t *testing.T) { diff --git a/agent/sandbox/v2/claude/session.go b/agent/sandbox/v2/claude/session.go index bb032b35..05333653 100644 --- a/agent/sandbox/v2/claude/session.go +++ b/agent/sandbox/v2/claude/session.go @@ -64,7 +64,27 @@ func (s *session) runStream(handler message.StreamFunc) (completed bool, err err return true, nil } - return false, s.waitForExit(parseErr) + exitErr := s.waitForExit(parseErr) + if exitErr != nil { + if handler != nil { + handler(message.ChunkError, []byte(exitErr.Error())) + } + return false, exitErr + } + + s.stderrMu.Lock() + stderrStr := strings.TrimSpace(s.stderr.String()) + s.stderrMu.Unlock() + if stderrStr != "" { + s.logger.Warn("claude exited with code 0 but stream incomplete and stderr present: %s", stderrStr) + errMsg := fmt.Errorf("claude CLI setup failed: %s", stderrStr) + if handler != nil { + handler(message.ChunkError, []byte(errMsg.Error())) + } + return false, errMsg + } + + return false, nil } // collectStderr reads stderr in a background goroutine.