Skip to content

Commit 6363450

Browse files
author
SqlRush
committed
Report API error request IDs in print events
1 parent 018a567 commit 6363450

4 files changed

Lines changed: 19 additions & 12 deletions

File tree

‎cmd/claude/main.go‎

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2156,6 +2156,7 @@ type printJSONResult struct {
21562156
Error string `json:"error,omitempty"`
21572157
ErrorType string `json:"error_type,omitempty"`
21582158
StatusCode int `json:"status_code,omitempty"`
2159+
RequestID string `json:"request_id,omitempty"`
21592160
Message *contracts.Message `json:"message,omitempty"`
21602161
StopReason string `json:"stop_reason,omitempty"`
21612162
Model string `json:"model,omitempty"`
@@ -2199,6 +2200,7 @@ type printStreamEvent struct {
21992200
Error string `json:"error,omitempty"`
22002201
ErrorType string `json:"error_type,omitempty"`
22012202
StatusCode int `json:"status_code,omitempty"`
2203+
RequestID string `json:"request_id,omitempty"`
22022204
IsError bool `json:"is_error,omitempty"`
22032205
DurationMS *int64 `json:"duration_ms,omitempty"`
22042206
DurationAPI *int64 `json:"duration_api_ms,omitempty"`
@@ -2467,21 +2469,21 @@ func writePrintStreamEvent(encoder *json.Encoder, event conversation.Event) erro
24672469
}
24682470
if event.Error != nil {
24692471
out.Error = event.Error.Error()
2470-
out.ErrorType, out.StatusCode = printAPIErrorMetadata(event.Error)
2472+
out.ErrorType, out.StatusCode, out.RequestID = printAPIErrorMetadata(event.Error)
24712473
}
24722474
return encoder.Encode(out)
24732475
}
24742476

2475-
func printAPIErrorMetadata(err error) (string, int) {
2477+
func printAPIErrorMetadata(err error) (string, int, string) {
24762478
var apiErr anthropic.APIError
24772479
if errors.As(err, &apiErr) {
2478-
return apiErr.Type, apiErr.StatusCode
2480+
return apiErr.Type, apiErr.StatusCode, apiErr.RequestID
24792481
}
24802482
var apiErrPtr *anthropic.APIError
24812483
if errors.As(err, &apiErrPtr) && apiErrPtr != nil {
2482-
return apiErrPtr.Type, apiErrPtr.StatusCode
2484+
return apiErrPtr.Type, apiErrPtr.StatusCode, apiErrPtr.RequestID
24832485
}
2484-
return "", 0
2486+
return "", 0, ""
24852487
}
24862488

24872489
func printStreamRetryFrom(retry *conversation.RetryInfo) *printStreamRetry {
@@ -2588,7 +2590,7 @@ func writePrintError(stdout io.Writer, runner conversation.Runner, err error, ou
25882590

25892591
func writePrintJSONError(stdout io.Writer, runner conversation.Runner, err error, duration time.Duration, apiDuration time.Duration, modelsAttempted []string) error {
25902592
encoder := json.NewEncoder(stdout)
2591-
errorType, statusCode := printAPIErrorMetadata(err)
2593+
errorType, statusCode, requestID := printAPIErrorMetadata(err)
25922594
envelope := printJSONResult{
25932595
Type: "result",
25942596
Subtype: "error",
@@ -2600,6 +2602,7 @@ func writePrintJSONError(stdout io.Writer, runner conversation.Runner, err error
26002602
Error: err.Error(),
26012603
ErrorType: errorType,
26022604
StatusCode: statusCode,
2605+
RequestID: requestID,
26032606
}
26042607
applyPrintJSONRuntime(&envelope, runner)
26052608
return encoder.Encode(envelope)
@@ -2609,14 +2612,15 @@ func writePrintStreamError(stdout io.Writer, runner conversation.Runner, err err
26092612
encoder := json.NewEncoder(stdout)
26102613
durationMS := durationMillis(duration)
26112614
durationAPI := durationMillis(apiDuration)
2612-
errorType, statusCode := printAPIErrorMetadata(err)
2615+
errorType, statusCode, requestID := printAPIErrorMetadata(err)
26132616
envelope := printStreamEvent{
26142617
Type: "error",
26152618
SessionID: runner.SessionID,
26162619
ModelsAttempted: append([]string(nil), modelsAttempted...),
26172620
Error: err.Error(),
26182621
ErrorType: errorType,
26192622
StatusCode: statusCode,
2623+
RequestID: requestID,
26202624
IsError: true,
26212625
DurationMS: &durationMS,
26222626
DurationAPI: &durationAPI,

‎cmd/claude/main_test.go‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2242,6 +2242,7 @@ func TestWritePrintStreamEventRetryIncludesAPIErrorMetadata(t *testing.T) {
22422242
Model: "sonnet",
22432243
Error: fmt.Errorf("request failed: %w", anthropic.APIError{
22442244
StatusCode: http.StatusTooManyRequests,
2245+
RequestID: "req_retry_1",
22452246
Type: "rate_limit_error",
22462247
Message: "try later",
22472248
}),
@@ -2260,7 +2261,7 @@ func TestWritePrintStreamEventRetryIncludesAPIErrorMetadata(t *testing.T) {
22602261
if err := json.Unmarshal(stdout.Bytes(), &event); err != nil {
22612262
t.Fatalf("invalid json %q: %v", stdout.String(), err)
22622263
}
2263-
if event["type"] != "retry" || event["error_type"] != "rate_limit_error" || event["status_code"] != float64(http.StatusTooManyRequests) {
2264+
if event["type"] != "retry" || event["error_type"] != "rate_limit_error" || event["status_code"] != float64(http.StatusTooManyRequests) || event["request_id"] != "req_retry_1" {
22642265
t.Fatalf("event = %#v", event)
22652266
}
22662267
}
@@ -2347,6 +2348,7 @@ func TestWritePrintJSONErrorIncludesAPIErrorMetadata(t *testing.T) {
23472348
var stdout bytes.Buffer
23482349
err := writePrintJSONError(&stdout, conversation.Runner{SessionID: "sess_error"}, anthropic.APIError{
23492350
StatusCode: http.StatusServiceUnavailable,
2351+
RequestID: "req_json_1",
23502352
Type: "overloaded_error",
23512353
Message: "temporarily overloaded",
23522354
}, time.Millisecond, 2*time.Millisecond, []string{"sonnet"})
@@ -2357,7 +2359,7 @@ func TestWritePrintJSONErrorIncludesAPIErrorMetadata(t *testing.T) {
23572359
if err := json.Unmarshal(stdout.Bytes(), &payload); err != nil {
23582360
t.Fatalf("invalid json stdout %q: %v", stdout.String(), err)
23592361
}
2360-
if payload["type"] != "result" || payload["subtype"] != "error" || payload["error_type"] != "overloaded_error" || payload["status_code"] != float64(http.StatusServiceUnavailable) {
2362+
if payload["type"] != "result" || payload["subtype"] != "error" || payload["error_type"] != "overloaded_error" || payload["status_code"] != float64(http.StatusServiceUnavailable) || payload["request_id"] != "req_json_1" {
23612363
t.Fatalf("payload = %#v", payload)
23622364
}
23632365
}
@@ -2385,6 +2387,7 @@ func TestWritePrintStreamErrorIncludesAPIErrorMetadata(t *testing.T) {
23852387
var stdout bytes.Buffer
23862388
err := writePrintStreamError(&stdout, conversation.Runner{SessionID: "sess_error"}, anthropic.APIError{
23872389
StatusCode: http.StatusUnauthorized,
2390+
RequestID: "req_stream_1",
23882391
Type: "authentication_error",
23892392
Message: "invalid api key",
23902393
}, time.Millisecond, 2*time.Millisecond, []string{"sonnet"})
@@ -2395,7 +2398,7 @@ func TestWritePrintStreamErrorIncludesAPIErrorMetadata(t *testing.T) {
23952398
if err := json.Unmarshal(stdout.Bytes(), &event); err != nil {
23962399
t.Fatalf("invalid json stdout %q: %v", stdout.String(), err)
23972400
}
2398-
if event["type"] != "error" || event["is_error"] != true || event["error_type"] != "authentication_error" || event["status_code"] != float64(http.StatusUnauthorized) {
2401+
if event["type"] != "error" || event["is_error"] != true || event["error_type"] != "authentication_error" || event["status_code"] != float64(http.StatusUnauthorized) || event["request_id"] != "req_stream_1" {
23992402
t.Fatalf("event = %#v", event)
24002403
}
24012404
}

‎docs/cc-100-roadmap.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1540,7 +1540,7 @@ M7 补充:terminal input parser 和 configurable keybinding name parser 现在
15401540

15411541
本轮补充:CLI JSON/NDJSON final result 和 structured error envelope 现在暴露 `models_attempted`,保留本轮请求实际尝试过的模型顺序;配合 retry breadcrumb,headless/SDK 调用方可以在成功或失败结果里审计 fallback 路径。
15421542

1543-
本轮补充:CLI JSON/NDJSON structured error 和 stream-json retry/error 事件现在会在底层错误为 Anthropic API error 时暴露 `error_type` 与 `status_code`,让 headless/SDK 调用方可以机器判定 rate limit、auth failure、overload 和 5xx fallback,而不必解析自然语言错误字符串。
1543+
本轮补充:CLI JSON/NDJSON structured error 和 stream-json retry/error 事件现在会在底层错误为 Anthropic API error 时暴露 `error_type`、`status_code` 和 `request_id`,让 headless/SDK 调用方可以机器判定 rate limit、auth failure、overload 和 5xx fallback,并把错误关联到 provider 请求日志,而不必解析自然语言错误字符串。
15441544

15451545
本轮补充:CLI headless runner 在 Anthropic client 初始化阶段失败时会保留已构造的 runner 元数据,因此缺凭证等 late setup error 的 JSON/NDJSON structured error result 也能输出 `cwd`、`session_id` 和 settings-derived runtime context。
15461546

‎docs/first-second-parity-audit.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ Anthropic API 和 conversation:
8585
- OAuth support now includes production OAuth config, scope parsing, Claude.ai scope detection, auth URL construction, PKCE verifier/challenge, state generation, and expiry checks.
8686
- Session/history support now includes CC-compatible prompt history references, pasted text/image placeholder parsing, paste-cache hashing and retrieval, `history.jsonl` append/load, current-session-first up-arrow ordering, ctrl+r-style deduped timestamped history, `CLAUDE_CODE_SKIP_PROMPT_HISTORY`, remote session event pagination helpers, lenient transcript loading, legacy progress parent-bridge recovery, compact-boundary pruning, snip removal/relink replay, metadata entry collection, leaf UUID calculation, conversation-chain reconstruction, orphaned parallel tool-result recovery, content-replacement record loading/reconstruction, tombstone metadata delete/relink replay, and tombstone-style transcript message removal with a size guard.
8787
- Anthropic API layer now covers streaming accumulation, usage update/accumulation semantics, non-streaming max token cap, thinking-budget adjustment, retry/backoff with `Retry-After` and `x-should-retry`, context-overflow `max_tokens` retry adjustment, beta-header dedupe, custom request headers, basic prompt cache breakpoint/cache-reference/cache-edits placement, prompt dump JSONL capture for init/new user messages/non-streaming responses/stream chunks, and CC-compatible USD cost calculation for known Claude models including cache read/write, web search requests, and Opus 4.6 fast-tier pricing.
88-
- Tool runtime now includes concurrency partitioning, ordered concurrent execution, interrupt behavior/defaults, max result size metadata, oversized result persistence, pre/post/permission-denied/permission-request hook dispatch, settings and local-plugin command-backed/HTTP hook execution for synchronous tool and conversation lifecycle hooks, hook-driven input updates/blocking/permission-request allow-deny, prompt/compact context injection, executor hook phase progress events, lifecycle progress events, retry/fallback model breadcrumbs, final-result/structured-error model attempt lists, structured API error status/type fields, headless stream-json progress events, snake_case token warning NDJSON payloads, lightweight compact NDJSON metadata, and pre-call cancellation checks.
88+
- Tool runtime now includes concurrency partitioning, ordered concurrent execution, interrupt behavior/defaults, max result size metadata, oversized result persistence, pre/post/permission-denied/permission-request hook dispatch, settings and local-plugin command-backed/HTTP hook execution for synchronous tool and conversation lifecycle hooks, hook-driven input updates/blocking/permission-request allow-deny, prompt/compact context injection, executor hook phase progress events, lifecycle progress events, retry/fallback model breadcrumbs, final-result/structured-error model attempt lists, structured API error status/type/request-id fields, headless stream-json progress events, snake_case token warning NDJSON payloads, lightweight compact NDJSON metadata, and pre-call cancellation checks.
8989
- Conversation runner can now use streaming clients, aggregate stream events into assistant messages, run tool calls through the orchestrator, preserve transcript append behavior, and apply CC-style per-message aggregate tool-result budget replacement before API requests with persisted replacement records for resume.
9090

9191
## Still Missing For 100% Compatibility

0 commit comments

Comments
 (0)