Skip to content

fix(client): classify request timeouts as timeout/exit 4 on all paths#232

Merged
somanshreddy merged 2 commits into
mainfrom
07-21-unify_timeout_classification
Jul 22, 2026
Merged

fix(client): classify request timeouts as timeout/exit 4 on all paths#232
somanshreddy merged 2 commits into
mainfrom
07-21-unify_timeout_classification

Conversation

@somanshreddy

Copy link
Copy Markdown
Collaborator

Scope

Surfaces: API (CLI) | Module: HTTP client / error classification

Supersedes #229 (auto-closed when its base #228 merged and the base branch was removed). Review feedback from #229 is addressed here — see commits on this branch.

Summary

A transport-level HTTP timeout was reported with a different error code and exit code depending on whether --wait was passed. It now consistently surfaces as timeout (exit 4) on every path.

Root cause

The same event — the per-request HTTP deadline elapsing before a response arrived — was classified two ways: plain Executenetwork_error / exit 1; --waittimeout / exit 4 via a fragile string-match on the Go error message. So a slow create/upload reported inconsistently, and the --wait detection depended on an error string that could drift.

Fix

Classify the timeout at the source in executeWithContext using net.Error.Timeout() — the robust detector, since http.Client.Timeout's wrapped error does not reliably satisfy errors.Is(err, context.DeadlineExceeded) (exactly why the old path string-matched). Both paths now return timeout / exit 4, and the dead string-match in translateCreateContextError is removed.

Also, from #229 review:

  • Write-safe hint (duplicate-create): the timeout hint is method-aware. A GET is safe to retry; a non-GET (create/write) may have committed server-side before the deadline and CLI writes carry no idempotency key, so its hint says "check status before retrying, to avoid a duplicate" — mirroring the poll-deadline guidance.
  • Budget in message: the timeout message names the applied per-operation budget (...after 120s) for support triage.
  • Retry-layer alignment: shouldRetry also short-circuits on net.Error.Timeout(), so the retry layer and the classifier agree a client timeout isn't retryable.

Scope of impact: a connection-refused / genuine transport failure still returns network_error (exit 1); the --wait poll-window-elapsed path is unchanged.

Breaking change

A plain (non---wait) request timeout now exits 4 instead of 1. The README exit-code table is updated. Diligence: scanned heygen-com for consumers branching on the CLI's network_error / exit 1 — none key on it, so no known internal consumer breaks.

Testing

New tests: a request that exceeds the client timeout surfaces timeout / exit 4; a POST (write) timeout carries the check-status-first hint and names the budget; a GET timeout does not carry create/duplicate guidance. Existing tests still pass: connection-refused → network_error (TestExecute_NetworkError), and the --wait poll-deadline test still yields timeout / exit 4. make lint clean.

somanshreddy and others added 2 commits July 22, 2026 21:36
A transport-level HTTP timeout was classified inconsistently: network_error
(exit 1) on the plain Execute path, but timeout (exit 4) under --wait (via a
fragile string-match on the Go error message). The same slow create/upload
therefore reported differently depending on whether --wait was passed.

Classify the timeout at the source in executeWithContext using net.Error.
Timeout() (robust where errors.Is(context.DeadlineExceeded) is not), so both
paths return timeout/exit 4. The now-dead message string-match in
translateCreateContextError is removed.

Behavior change: a plain (non --wait) request timeout now exits 4 instead of
1. README exit-code doc updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ection

Review feedback (#229):
- A transport timeout on a create emitted a generic "retry" hint, but CLI
  writes carry no idempotency key, so a blind retry can duplicate a create
  (and its charge). Make the hint method-aware: GET -> safe to retry; non-GET
  -> "check status before retrying", mirroring the poll-deadline guidance.
- Surface the applied per-operation budget in the timeout message ("...after
  120s") so support can see which timeout tripped.
- shouldRetry now also short-circuits on net.Error.Timeout(), matching the
  executor's classifier, so the retry layer and error classifier agree that a
  client timeout is not worth retrying (previously a client-timeout on an
  idempotent GET fell through to a wasted retry attempt).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@somanshreddy
somanshreddy merged commit 9a3a48a into main Jul 22, 2026
8 of 9 checks passed
@somanshreddy
somanshreddy deleted the 07-21-unify_timeout_classification branch July 22, 2026 21:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant