Skip to content

Classify transport errors instead of returning free-form strings #24

Description

@carpentry-agent

Every failure path in Client returns (Result.Error String) and the strings come from three different owners:

  • http-client prose: "missing host in URL", "redirect without Location header", "redirect with empty Location header", "too many redirects (max %d)", "incomplete HTTP headers"
  • uri prose: "Invalid URI: ..."
  • whatever the transport hands back: System.error-text (i.e. strerror(errno)) for TCP, tls_error() / the OpenSSL error string for TLS, and Response.parse's "Malformed response: ..." from http

A caller that wants to retry has to tell three things apart that the string cannot express:

  1. Retryable vs permanent. A refused or reset connection is worth another attempt; a malformed URL, an unfollowable redirect chain or a certificate that does not verify is not. Today that is prefix-matching English prose, which silently reclassifies on a wording change.
  2. Connect-stage vs post-send. build-and-send writes the entire request before the first read (http-client.carp:273), so "incomplete HTTP headers" and every Response.parse failure mean the request was delivered. A caller retrying those re-executes work the server already did — for an LLM endpoint, a billed generation.
  3. DNS failures at all. TcpStream.connect returns s unchanged when getaddrinfo fails (socket 0.2.3 src/tcp_stream.h:27) and the caller then reports strerror(errno) — but getaddrinfo does not set errno, so the message is stale. Measured here against http://no-such-host.invalid/:
DNS-ERR: [Invalid argument]

That is EINVAL left over from earlier in the process, not a DNS error, and it is indistinguishable from a genuine EINVAL.

Something like a ClientError sum type (Uri, Dns, Connect, Tls, Send, Receive, Parse, Redirect), or at minimum an exported Client.retryable-error? predicate that owns the classification next to the code that produces the strings, would let downstream retry loops be correct rather than approximately correct.

Downstream: carpentry-org/llm#19 currently keeps a prefix list of these strings and a comment pointing here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions