Files
coder/coderd/x/chatd/chaterror/payload_test.go
T
Ethan 21c2acbad5 fix: refine chat retry status UX (#23651)
Follow-up to #23282. The retry and terminal error callouts had a few UX
oddities:

- Auto-retrying states reused backend error text that said "Please try
again" even while the UI was already retrying on behalf of the user.
- Terminal error states also said "Please try again" with no action the
user could take.
- `startup_timeout` had no specific title or retry copy — it fell
through to the generic "Retrying request" heading.
- The kind pill showed raw enum values like `startup_timeout` and
`rate_limit`.
- Terminal error metadata showed a "Retryable" / "Not retryable" label
that does not help users.
- A separate "Provider anthropic" metadata row duplicated information
already present in the message body.
- The `usage-limit` error kind used a hyphen while every backend kind
uses underscores.

Changes:

**Backend (`chaterror/message.go`)**

- Split message generation into `terminalMessage()` and
`retryMessage()`, replacing the old `userFacingMessage()`.
- Terminal messages include HTTP status codes and actionable guidance
(e.g. "Check the API key, permissions, and billing settings.").
- Retry messages are clean factual statements without status codes or
remediation, suitable for the retry countdown UI (e.g. "Anthropic is
temporarily overloaded.").
- Removed "Please try again" / "Please try again later" from all paths.
- `StreamRetryPayload` calls `retryMessage()` instead of forwarding
`classified.Message`.

**Frontend**

- Removed the parallel frontend message-generation system:
`getRetryMessage()`, `getProviderDisplayName()`,
`getRetryProviderSubject()`, and the `PROVIDER_DISPLAY_NAMES` map are
all deleted from `chatStatusHelpers.ts`.
- `liveStatusModel.ts` passes `retryState.error` through directly — the
backend owns the copy.
- Added specific title and retry copy for `startup_timeout`, and
extended the title mapping to cover `auth` and `config`.
- Kind pills now show humanized labels ("Startup timeout", "Rate limit",
etc.) instead of raw enum strings.
- Removed the redundant "Provider anthropic" metadata row.
- Removed the terminal "Retryable" / "Not retryable" badge.
- Normalized `"usage-limit"` → `"usage_limit"` and added it to
`ChatProviderFailureKind` so all error kinds follow the same underscore
convention and live in one enum.

Refs #23282.
2026-03-26 17:37:27 +11:00

61 lines
1.7 KiB
Go

package chaterror_test
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd/x/chatd/chaterror"
"github.com/coder/coder/v2/codersdk"
)
func TestStreamErrorPayloadUsesNormalizedClassification(t *testing.T) {
t.Parallel()
classified := chaterror.Classify(
xerrors.New("azure openai received status 429 from upstream"),
)
payload := chaterror.StreamErrorPayload(classified)
require.Equal(t, &codersdk.ChatStreamError{
Message: "Azure OpenAI is rate limiting requests (HTTP 429).",
Kind: chaterror.KindRateLimit,
Provider: "azure",
Retryable: true,
StatusCode: 429,
}, payload)
}
func TestStreamErrorPayloadNilForEmptyClassification(t *testing.T) {
t.Parallel()
require.Nil(t, chaterror.StreamErrorPayload(chaterror.ClassifiedError{}))
}
func TestStreamRetryPayloadUsesNormalizedClassification(t *testing.T) {
t.Parallel()
delay := 3 * time.Second
startedAt := time.Now()
payload := chaterror.StreamRetryPayload(2, delay, chaterror.ClassifiedError{
Message: "OpenAI returned an unexpected error (HTTP 503).",
Kind: chaterror.KindGeneric,
Provider: "openai",
Retryable: true,
StatusCode: 503,
})
require.NotNil(t, payload)
require.Equal(t, 2, payload.Attempt)
require.Equal(t, delay.Milliseconds(), payload.DelayMs)
// Retry messages omit the HTTP status code; the status code is
// surfaced separately in the payload's StatusCode field.
require.Equal(t, "OpenAI returned an unexpected error.", payload.Error)
require.Equal(t, chaterror.KindGeneric, payload.Kind)
require.Equal(t, "openai", payload.Provider)
require.Equal(t, 503, payload.StatusCode)
require.WithinDuration(t, startedAt.Add(delay), payload.RetryingAt, time.Second)
}