diff --git a/codeagent-wrapper/internal/app/cli.go b/codeagent-wrapper/internal/app/cli.go index 6ce7bf8..12bddcb 100644 --- a/codeagent-wrapper/internal/app/cli.go +++ b/codeagent-wrapper/internal/app/cli.go @@ -648,6 +648,12 @@ func runSingleMode(cfg *Config, name string) int { return result.ExitCode } + // Validate that we got a meaningful output message + if strings.TrimSpace(result.Message) == "" { + logError(fmt.Sprintf("no output message: backend=%s returned empty result.Message with exit_code=0", cfg.Backend)) + return 1 + } + fmt.Println(result.Message) if result.SessionID != "" { fmt.Printf("\n---\nSESSION_ID: %s\n", result.SessionID) diff --git a/codeagent-wrapper/internal/app/main_test.go b/codeagent-wrapper/internal/app/main_test.go index b6d3d93..c879fc7 100644 --- a/codeagent-wrapper/internal/app/main_test.go +++ b/codeagent-wrapper/internal/app/main_test.go @@ -1913,6 +1913,37 @@ func TestRun_PassesReasoningEffortToTaskSpec(t *testing.T) { } } +func TestRun_NoOutputMessage_ReturnsExitCode1AndWritesStderr(t *testing.T) { + defer resetTestHooks() + cleanupLogsFn = func() (CleanupStats, error) { return CleanupStats{}, nil } + t.Setenv("TMPDIR", t.TempDir()) + + selectBackendFn = func(name string) (Backend, error) { + return testBackend{name: name, command: "echo"}, nil + } + + runTaskFn = func(task TaskSpec, silent bool, timeout int) TaskResult { + return TaskResult{ExitCode: 0, Message: ""} + } + + isTerminalFn = func() bool { return true } + stdinReader = strings.NewReader("") + + os.Args = []string{"codeagent-wrapper", "task"} + + var code int + errOutput := captureStderr(t, func() { + code = run() + }) + + if code != 1 { + t.Fatalf("run() exit=%d, want 1", code) + } + if !strings.Contains(errOutput, "no output message") { + t.Fatalf("stderr missing sentinel error text; got:\n%s", errOutput) + } +} + func TestRunBuildCodexArgs_NewMode(t *testing.T) { const key = "CODEX_BYPASS_SANDBOX" t.Setenv(key, "false")