Skip to content

Commit b204ca9

Browse files
authored
fix(codeagent-wrapper): keep logs + surface parsed error output (#152)
Fixes #150
1 parent a39bf72 commit b204ca9

3 files changed

Lines changed: 112 additions & 17 deletions

File tree

codeagent-wrapper/internal/app/cli.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,10 +200,9 @@ func runWithLoggerAndCleanup(fn func() int) (exitCode int) {
200200
for _, entry := range entries {
201201
fmt.Fprintln(os.Stderr, entry)
202202
}
203-
fmt.Fprintf(os.Stderr, "Log file: %s (deleted)\n", logger.Path())
203+
fmt.Fprintf(os.Stderr, "Log file: %s\n", logger.Path())
204204
}
205205
}
206-
_ = logger.RemoveLogFile()
207206
}()
208207
defer runCleanupHook()
209208

@@ -733,6 +732,13 @@ func runSingleMode(cfg *Config, name string) int {
733732
}
734733

735734
if exitCode != 0 {
735+
// Surface any parsed backend output even on non-zero exit to avoid "(no output)" in tool runners.
736+
if strings.TrimSpace(result.Message) != "" {
737+
fmt.Println(result.Message)
738+
if result.SessionID != "" {
739+
fmt.Printf("\n---\nSESSION_ID: %s\n", result.SessionID)
740+
}
741+
}
736742
return exitCode
737743
}
738744

codeagent-wrapper/internal/app/main_test.go

Lines changed: 95 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4635,9 +4635,9 @@ func TestRun_ExplicitStdinReadError(t *testing.T) {
46354635
if !strings.Contains(logOutput, "Failed to read stdin: broken stdin") {
46364636
t.Fatalf("log missing read error entry, got %q", logOutput)
46374637
}
4638-
// Log file is always removed after completion (new behavior)
4639-
if _, err := os.Stat(logPath); !os.IsNotExist(err) {
4640-
t.Fatalf("log file should be removed after completion")
4638+
// Log file should remain for inspection; cleanup is handled via `codeagent-wrapper cleanup`.
4639+
if _, err := os.Stat(logPath); err != nil {
4640+
t.Fatalf("expected log file to exist after completion: %v", err)
46414641
}
46424642
}
46434643

@@ -4653,6 +4653,51 @@ func TestRun_CommandFails(t *testing.T) {
46534653
}
46544654
}
46554655

4656+
func TestRun_NonZeroExitPrintsParsedMessage(t *testing.T) {
4657+
defer resetTestHooks()
4658+
4659+
tempDir := t.TempDir()
4660+
var scriptPath string
4661+
if runtime.GOOS == "windows" {
4662+
scriptPath = filepath.Join(tempDir, "codex.bat")
4663+
script := "@echo off\r\n" +
4664+
"echo {\"type\":\"thread.started\",\"thread_id\":\"tid\"}\r\n" +
4665+
"echo {\"type\":\"item.completed\",\"item\":{\"type\":\"agent_message\",\"text\":\"parsed-error\"}}\r\n" +
4666+
"exit /b 1\r\n"
4667+
if err := os.WriteFile(scriptPath, []byte(script), 0o755); err != nil {
4668+
t.Fatalf("failed to write script: %v", err)
4669+
}
4670+
} else {
4671+
scriptPath = filepath.Join(tempDir, "codex.sh")
4672+
script := `#!/bin/sh
4673+
printf '%s\n' '{"type":"thread.started","thread_id":"tid"}'
4674+
printf '%s\n' '{"type":"item.completed","item":{"type":"agent_message","text":"parsed-error"}}'
4675+
sleep 0.05
4676+
exit 1
4677+
`
4678+
if err := os.WriteFile(scriptPath, []byte(script), 0o755); err != nil {
4679+
t.Fatalf("failed to write script: %v", err)
4680+
}
4681+
}
4682+
4683+
restore := withBackend(scriptPath, func(cfg *Config, targetArg string) []string { return []string{} })
4684+
defer restore()
4685+
4686+
os.Args = []string{"codeagent-wrapper", "task"}
4687+
stdinReader = strings.NewReader("")
4688+
isTerminalFn = func() bool { return true }
4689+
4690+
var exitCode int
4691+
output := captureOutput(t, func() { exitCode = run() })
4692+
if exitCode != 1 {
4693+
t.Fatalf("exit=%d, want 1", exitCode)
4694+
}
4695+
4696+
if !strings.Contains(output, "parsed-error") {
4697+
t.Fatalf("stdout=%q, want parsed backend message", output)
4698+
}
4699+
}
4700+
46564701
func TestRun_InvalidBackend(t *testing.T) {
46574702
defer resetTestHooks()
46584703
os.Args = []string{"codeagent-wrapper", "--backend", "unknown", "task"}
@@ -4732,9 +4777,9 @@ func TestRun_PipedTaskReadError(t *testing.T) {
47324777
if !strings.Contains(logOutput, "Failed to read piped stdin: read stdin: pipe failure") {
47334778
t.Fatalf("log missing piped read error, got %q", logOutput)
47344779
}
4735-
// Log file is always removed after completion (new behavior)
4736-
if _, err := os.Stat(logPath); !os.IsNotExist(err) {
4737-
t.Fatalf("log file should be removed after completion")
4780+
// Log file should remain for inspection; cleanup is handled via `codeagent-wrapper cleanup`.
4781+
if _, err := os.Stat(logPath); err != nil {
4782+
t.Fatalf("expected log file to exist after completion: %v", err)
47384783
}
47394784
}
47404785

@@ -4788,12 +4833,12 @@ func TestRun_LoggerLifecycle(t *testing.T) {
47884833
if !fileExisted {
47894834
t.Fatalf("log file was not present during run")
47904835
}
4791-
if _, err := os.Stat(logPath); !os.IsNotExist(err) {
4792-
t.Fatalf("log file should be removed on success, but it exists")
4836+
if _, err := os.Stat(logPath); err != nil {
4837+
t.Fatalf("expected log file to exist on success: %v", err)
47934838
}
47944839
}
47954840

4796-
func TestRun_LoggerRemovedOnSignal(t *testing.T) {
4841+
func TestRun_LoggerKeptOnSignal(t *testing.T) {
47974842
if runtime.GOOS == "windows" {
47984843
t.Skip("signal-based test is not supported on Windows")
47994844
}
@@ -4807,7 +4852,8 @@ func TestRun_LoggerRemovedOnSignal(t *testing.T) {
48074852
defer signal.Reset(syscall.SIGINT, syscall.SIGTERM)
48084853

48094854
// Set shorter delays for faster test
4810-
_ = executor.SetForceKillDelay(1)
4855+
restoreForceKillDelay := executor.SetForceKillDelay(1)
4856+
defer restoreForceKillDelay()
48114857

48124858
tempDir := setTempDirEnv(t, t.TempDir())
48134859
logPath := filepath.Join(tempDir, fmt.Sprintf("codeagent-wrapper-%d.log", os.Getpid()))
@@ -4830,13 +4876,19 @@ printf '%s\n' '{"type":"item.completed","item":{"type":"agent_message","text":"l
48304876
exitCh := make(chan int, 1)
48314877
go func() { exitCh <- run() }()
48324878

4833-
deadline := time.Now().Add(1 * time.Second)
4879+
ready := false
4880+
deadline := time.Now().Add(2 * time.Second)
48344881
for time.Now().Before(deadline) {
4835-
if _, err := os.Stat(logPath); err == nil {
4882+
data, err := os.ReadFile(logPath)
4883+
if err == nil && strings.Contains(string(data), "Starting ") {
4884+
ready = true
48364885
break
48374886
}
48384887
time.Sleep(10 * time.Millisecond)
48394888
}
4889+
if !ready {
4890+
t.Fatalf("logger did not become ready before deadline")
4891+
}
48404892

48414893
if proc, err := os.FindProcess(os.Getpid()); err == nil && proc != nil {
48424894
_ = proc.Signal(syscall.SIGINT)
@@ -4852,9 +4904,9 @@ printf '%s\n' '{"type":"item.completed","item":{"type":"agent_message","text":"l
48524904
if exitCode != 130 {
48534905
t.Fatalf("exit code = %d, want 130", exitCode)
48544906
}
4855-
// Log file is always removed after completion (new behavior)
4856-
if _, err := os.Stat(logPath); !os.IsNotExist(err) {
4857-
t.Fatalf("log file should be removed after completion")
4907+
// Log file should remain for inspection; cleanup is handled via `codeagent-wrapper cleanup`.
4908+
if _, err := os.Stat(logPath); err != nil {
4909+
t.Fatalf("expected log file to exist after completion: %v", err)
48584910
}
48594911
}
48604912

@@ -5115,6 +5167,34 @@ func TestParallelLogPathInSerialMode(t *testing.T) {
51155167
}
51165168
}
51175169

5170+
func TestRun_KeptLogFileOnSuccess(t *testing.T) {
5171+
defer resetTestHooks()
5172+
5173+
tempDir := setTempDirEnv(t, t.TempDir())
5174+
5175+
os.Args = []string{"codeagent-wrapper", "do-stuff"}
5176+
stdinReader = strings.NewReader("")
5177+
isTerminalFn = func() bool { return true }
5178+
codexCommand = createFakeCodexScript(t, "cli-session", "ok")
5179+
buildCodexArgsFn = func(cfg *Config, targetArg string) []string { return []string{} }
5180+
cleanupLogsFn = nil
5181+
5182+
var exitCode int
5183+
_ = captureStderr(t, func() {
5184+
_ = captureOutput(t, func() {
5185+
exitCode = run()
5186+
})
5187+
})
5188+
if exitCode != 0 {
5189+
t.Fatalf("run() exit = %d, want 0", exitCode)
5190+
}
5191+
5192+
expectedLog := filepath.Join(tempDir, fmt.Sprintf("codeagent-wrapper-%d.log", os.Getpid()))
5193+
if _, err := os.Stat(expectedLog); err != nil {
5194+
t.Fatalf("expected log file to exist: %v", err)
5195+
}
5196+
}
5197+
51185198
func TestRun_CLI_Success(t *testing.T) {
51195199
defer resetTestHooks()
51205200
os.Args = []string{"codeagent-wrapper", "do-things"}

codeagent-wrapper/internal/executor/executor.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,6 +1435,15 @@ waitLoop:
14351435
logErrorFn(fmt.Sprintf("%s exited with status %d", commandName, code))
14361436
result.ExitCode = code
14371437
result.Error = attachStderr(fmt.Sprintf("%s exited with status %d", commandName, code))
1438+
// Preserve parsed output when the backend exits non-zero (e.g. API error with stream-json output).
1439+
result.Message = parsed.message
1440+
result.SessionID = parsed.threadID
1441+
if stdoutLogger != nil {
1442+
stdoutLogger.Flush()
1443+
}
1444+
if stderrLogger != nil {
1445+
stderrLogger.Flush()
1446+
}
14381447
return result
14391448
}
14401449
logErrorFn(commandName + " error: " + waitErr.Error())

0 commit comments

Comments
 (0)