From cefc15aa7d42bc3ec178da669a7887387dc8a55c Mon Sep 17 00:00:00 2001 From: jordan Date: Tue, 10 Feb 2026 17:55:46 -0700 Subject: [PATCH] fix(worker): include stdout in error messages when Claude command fails Auth errors like "OAuth token has expired" were lost because Claude writes them to stdout, not stderr. The error message only showed kubectl's generic "command terminated with exit code 1". Now includes both stdout and stderr in the error, making failures immediately diagnosable. Co-Authored-By: Claude Opus 4.6 --- .../adapter/codeagent/claudecode/adapter.go | 22 +++++++++++++------ internal/worker/build_executor.go | 8 ++++++- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/internal/adapter/codeagent/claudecode/adapter.go b/internal/adapter/codeagent/claudecode/adapter.go index ec7841f..3347c1c 100644 --- a/internal/adapter/codeagent/claudecode/adapter.go +++ b/internal/adapter/codeagent/claudecode/adapter.go @@ -162,6 +162,8 @@ func (a *Adapter) Execute(ctx context.Context, req *domain.AgentRequest, handler } // Determine exit code and error + stdoutStr := finalOutput.String() + stderrStr := stderrOutput.String() if cmdErr != nil { if exitErr, ok := cmdErr.(*exec.ExitError); ok { result.ExitCode = exitErr.ExitCode() @@ -169,17 +171,17 @@ func (a *Adapter) Execute(ctx context.Context, req *domain.AgentRequest, handler result.ExitCode = 1 result.Error = cmdErr } - // Include stderr and troubleshooting help in error - result.Error = a.buildErrorWithHelp(result.Error, stderrOutput.String(), namespace, podName) + // Include stdout, stderr, and troubleshooting help in error + result.Error = a.buildErrorWithHelp(result.Error, stderrStr, stdoutStr, namespace, podName) } else if parseErr != nil { result.ExitCode = 1 - result.Error = a.buildErrorWithHelp(parseErr, stderrOutput.String(), namespace, podName) + result.Error = a.buildErrorWithHelp(parseErr, stderrStr, stdoutStr, namespace, podName) } else if resultMsg != nil && !resultMsg.IsSuccess() { result.ExitCode = 1 if resultMsg.Error != "" { - result.Error = a.buildErrorWithHelp(fmt.Errorf("%s", resultMsg.Error), stderrOutput.String(), namespace, podName) + result.Error = a.buildErrorWithHelp(fmt.Errorf("%s", resultMsg.Error), stderrStr, stdoutStr, namespace, podName) } else { - result.Error = a.buildErrorWithHelp(nil, stderrOutput.String(), namespace, podName) + result.Error = a.buildErrorWithHelp(nil, stderrStr, stdoutStr, namespace, podName) } } @@ -320,8 +322,8 @@ func (a *Adapter) streamStderrCapture(r io.Reader, handler domain.AgentEventHand } } -// buildErrorWithHelp creates an error message with stderr output and troubleshooting help. -func (a *Adapter) buildErrorWithHelp(err error, stderr, namespace, podName string) error { +// buildErrorWithHelp creates an error message with captured output and troubleshooting help. +func (a *Adapter) buildErrorWithHelp(err error, stderr, stdout, namespace, podName string) error { var msg strings.Builder if err != nil { @@ -330,6 +332,12 @@ func (a *Adapter) buildErrorWithHelp(err error, stderr, namespace, podName strin msg.WriteString("claude command failed") } + // Include stdout if it contains useful output (e.g. auth errors that Claude writes to stdout) + if stdout != "" { + msg.WriteString("\n\noutput:\n") + msg.WriteString(stdout) + } + // Include stderr if available if stderr != "" { msg.WriteString("\n\nstderr:\n") diff --git a/internal/worker/build_executor.go b/internal/worker/build_executor.go index 7754d6a..8d9f0a0 100644 --- a/internal/worker/build_executor.go +++ b/internal/worker/build_executor.go @@ -221,9 +221,15 @@ func (b *BuildExecutor) Execute(ctx context.Context, task *domain.WorkTask) *dom } } + // Use streamed output, but fall back to agent's captured output if streaming missed it + output := outputBuilder.String() + if output == "" && agentResult.FinalOutput != "" { + output = agentResult.FinalOutput + } + result := &domain.BuildResult{ Success: agentResult.Success(), - Output: outputBuilder.String(), + Output: output, DurationMs: time.Since(start).Milliseconds(), Artifacts: make(map[string]string), }