fix: Include stderr and troubleshooting help in Claude Code errors

When Claude fails to execute, error messages now include:
- Captured stderr output from the failed command
- Troubleshooting commands to exec into pod and run `claude login`

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jordan 2026-01-29 23:12:01 -07:00
parent 4354f96351
commit e9984ebc07
3 changed files with 61 additions and 7 deletions

15
changelog/v0.10.12.md Normal file
View File

@ -0,0 +1,15 @@
# v0.10.12
**Released:** 2026-01-29
## Changes
fix: Include stderr and troubleshooting help in Claude Code error messages
When Claude fails to execute, the error now includes:
- The actual stderr output from the failed command
- Troubleshooting commands to exec into the pod and run `claude login`
---
**Image:** `ghcr.io/orchard9/rdev-api:v0.10.12`

View File

@ -24,7 +24,7 @@ spec:
serviceAccountName: rdev-api
containers:
- name: rdev-api
image: ghcr.io/orchard9/rdev-api:v0.10.11
image: ghcr.io/orchard9/rdev-api:v0.10.12
imagePullPolicy: Always
ports:

View File

@ -131,6 +131,7 @@ func (a *Adapter) Execute(ctx context.Context, req *domain.AgentRequest, handler
// Stream and parse output
var wg sync.WaitGroup
var finalOutput strings.Builder
var stderrOutput strings.Builder
var parseErr error
var resultMsg *StreamMessage
@ -142,10 +143,10 @@ func (a *Adapter) Execute(ctx context.Context, req *domain.AgentRequest, handler
resultMsg, parseErr = a.parseStreamOutput(stdout, handler, &finalOutput)
}()
// Stream stderr as error events
// Stream stderr as error events and capture for error message
go func() {
defer wg.Done()
a.streamStderr(stderr, handler)
a.streamStderrCapture(stderr, handler, &stderrOutput)
}()
wg.Wait()
@ -168,13 +169,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)
} else if parseErr != nil {
result.ExitCode = 1
result.Error = parseErr
result.Error = a.buildErrorWithHelp(parseErr, stderrOutput.String(), namespace, podName)
} else if resultMsg != nil && !resultMsg.IsSuccess() {
result.ExitCode = 1
if resultMsg.Error != "" {
result.Error = fmt.Errorf("%s", resultMsg.Error)
result.Error = a.buildErrorWithHelp(fmt.Errorf("%s", resultMsg.Error), stderrOutput.String(), namespace, podName)
} else {
result.Error = a.buildErrorWithHelp(nil, stderrOutput.String(), namespace, podName)
}
}
@ -274,8 +279,8 @@ func (a *Adapter) parseStreamOutput(r io.Reader, handler domain.AgentEventHandle
return resultMsg, nil
}
// streamStderr reads stderr and emits error events.
func (a *Adapter) streamStderr(r io.Reader, handler domain.AgentEventHandler) {
// streamStderrCapture reads stderr, emits error events, and captures output.
func (a *Adapter) streamStderrCapture(r io.Reader, handler domain.AgentEventHandler, capture *strings.Builder) {
scanner := bufio.NewScanner(r)
buf := make([]byte, 0, 64*1024)
scanner.Buffer(buf, 1024*1024)
@ -292,9 +297,43 @@ func (a *Adapter) streamStderr(r io.Reader, handler domain.AgentEventHandler) {
Content: line,
Stream: "stderr",
})
// Capture stderr for error message (limit to 4KB)
if capture.Len() < 4096 {
if capture.Len() > 0 {
capture.WriteString("\n")
}
capture.WriteString(line)
}
}
}
// buildErrorWithHelp creates an error message with stderr output and troubleshooting help.
func (a *Adapter) buildErrorWithHelp(err error, stderr, namespace, podName string) error {
var msg strings.Builder
if err != nil {
msg.WriteString(err.Error())
} else {
msg.WriteString("claude command failed")
}
// Include stderr if available
if stderr != "" {
msg.WriteString("\n\nstderr:\n")
msg.WriteString(stderr)
}
// Add troubleshooting help
msg.WriteString("\n\n---\nTroubleshooting:\n")
msg.WriteString("If Claude is not authenticated, run:\n")
fmt.Fprintf(&msg, " kubectl exec -it -n %s %s -- claude login\n", namespace, podName)
msg.WriteString("\nTo test Claude manually:\n")
fmt.Fprintf(&msg, " kubectl exec -it -n %s %s -- claude -p \"hello\"\n", namespace, podName)
return fmt.Errorf("%s", msg.String())
}
// Cancel attempts to cancel a running session.
func (a *Adapter) Cancel(ctx context.Context, sessionID string) error {
a.sessionsMu.Lock()