fix(providers): clear timeout error message and fallback on CLI provider timeout
When a CLI provider subprocess was killed due to request_timeout, the error
reported was the raw subprocess signal ("signal: killed"), giving the user
no indication that a timeout was the cause. Additionally, the fallback chain
would not trigger because ClassifyError used == to match context.DeadlineExceeded,
which does not match wrapped errors.
- Each CLI provider now checks ctx.Err() == context.DeadlineExceeded after
cmd.Run() fails and returns a descriptive error (e.g. "claude cli timed out
after 30s") that wraps context.DeadlineExceeded
- ClassifyError updated to use errors.Is instead of == when checking for
context.DeadlineExceeded, so wrapped timeout errors correctly classify as
FailoverTimeout and trigger fallback to the next candidate in the chain
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
df967fe63c
commit
56ac14c0e5
4 changed files with 11 additions and 1 deletions
|
|
@ -63,6 +63,9 @@ func (p *ClaudeCliProvider) Chat(
|
||||||
cmd.Stderr = &stderr
|
cmd.Stderr = &stderr
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
|
if ctx.Err() == context.DeadlineExceeded {
|
||||||
|
return nil, fmt.Errorf("claude cli timed out after %s: %w", p.timeout, context.DeadlineExceeded)
|
||||||
|
}
|
||||||
stderrStr := strings.TrimSpace(stderr.String())
|
stderrStr := strings.TrimSpace(stderr.String())
|
||||||
stdoutStr := strings.TrimSpace(stdout.String())
|
stdoutStr := strings.TrimSpace(stdout.String())
|
||||||
switch {
|
switch {
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,9 @@ func (p *CodexCliProvider) Chat(
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if ctx.Err() == context.DeadlineExceeded {
|
||||||
|
return nil, fmt.Errorf("codex cli timed out after %s: %w", p.timeout, context.DeadlineExceeded)
|
||||||
|
}
|
||||||
if ctx.Err() == context.Canceled {
|
if ctx.Err() == context.Canceled {
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package providers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
@ -114,7 +115,7 @@ func ClassifyError(err error, provider, model string) *FailoverError {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Context deadline exceeded: treat as timeout, always fallback.
|
// Context deadline exceeded: treat as timeout, always fallback.
|
||||||
if err == context.DeadlineExceeded {
|
if errors.Is(err, context.DeadlineExceeded) {
|
||||||
return &FailoverError{
|
return &FailoverError{
|
||||||
Reason: FailoverTimeout,
|
Reason: FailoverTimeout,
|
||||||
Provider: provider,
|
Provider: provider,
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,9 @@ func (p *GeminiCliProvider) Chat(
|
||||||
cmd.Stderr = &stderr
|
cmd.Stderr = &stderr
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
|
if ctx.Err() == context.DeadlineExceeded {
|
||||||
|
return nil, fmt.Errorf("gemini cli timed out after %s: %w", p.timeout, context.DeadlineExceeded)
|
||||||
|
}
|
||||||
stderrStr := strings.TrimSpace(stderr.String())
|
stderrStr := strings.TrimSpace(stderr.String())
|
||||||
stdoutStr := strings.TrimSpace(stdout.String())
|
stdoutStr := strings.TrimSpace(stdout.String())
|
||||||
switch {
|
switch {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue