Skip to content

Name the class of a transport failure, not just "request failed" - #64

Merged
YellowSnnowmann merged 1 commit into
tinyhumansai:mainfrom
YellowSnnowmann:feat/transport-error-diagnosis
Aug 19, 2026
Merged

Name the class of a transport failure, not just "request failed"#64
YellowSnnowmann merged 1 commit into
tinyhumansai:mainfrom
YellowSnnowmann:feat/transport-error-diagnosis

Conversation

@YellowSnnowmann

Copy link
Copy Markdown
Contributor

Found by testing the demo UI against a real hosted engine. A store against Cognee's cloud endpoint reported:

memory API request to api.cognee.ai failed

True and useless. The endpoint accepted TCP then aborted the TLS handshake — indistinguishable, from that message, from a DNS typo, a firewall, or a dead service. Each sends an operator to a different runbook. reqwest's own Display is one clause ("error sending request"); the cause lives one or more source() hops down, where a host logging the top line never sees it.

After

Case Message
TLS abort … api.cognee.ai: TLS failed — the endpoint answered on the port but could not establish a secure connection; check that the URL is the engine's real API host (client error (Connect): received fatal alert: InternalError)
bad hostname … : the host could not be resolved — check the URL (dns error: failed to lookup address information…)
nothing listening … : could not connect — check the URL and that the service is reachable (tcp connect error: Connection refused)
slow endpoint … : timed out

Two things the live run taught that reasoning did not

  1. is_connect() is also true for DNS and TLS failures. Testing it first collapses every class into "could not connect" — which is exactly what my first version did, and it looked correct until run against a real broken endpoint.
  2. rustls never says "TLS". A handshake abort reads received fatal alert: InternalError. Matching on the word "tls" would have missed the precise case this was written for; the needles match rustls' actual wording.

Both traps are pinned. The classifier is a free function purely so the order is testable — I reintroduced the is_connect-first bug to confirm a_rustls_handshake_abort_is_named_tls_not_connect fails, then restored it.

Credential is never included: host, class, and transport chain only.

Validation: cargo test -p tinymemory-remote 24 passed (19 existing + 5 ordering tests), clippy clean, fmt clean. Also verified end-to-end through the demo consumer against the live failing endpoint.

A remote store against Cognee's hosted endpoint reported:

    memory API request to api.cognee.ai failed

which is true and useless. The endpoint accepted TCP and then aborted
the TLS handshake; the operator cannot tell that from a DNS typo, a
firewall, or a service that is simply down, and each sends them to a
different runbook. `reqwest`'s own Display is one clause -- "error
sending request" -- and the cause lives one or more `source()` hops
down, which a host logging only the top line never sees.

Failures now name their class and carry the chain:

    memory API request to api.cognee.ai: TLS failed — the endpoint
    answered on the port but could not establish a secure connection;
    check that the URL is the engine's real API host (client error
    (Connect): received fatal alert: InternalError)

    memory API request to no-such-host.invalid: the host could not be
    resolved — check the URL (dns error: failed to lookup address
    information: nodename nor servname provided)

    memory API request to 127.0.0.1: could not connect — check the URL
    and that the service is reachable (tcp connect error: Connection
    refused (os error 61))

Two things a live run taught that reasoning did not:

- `is_connect()` is ALSO true for DNS and TLS failures, so testing it
  first collapses every class into "could not connect". That is what
  the first version did. The specific classes are checked first now.
- rustls never says "TLS". A handshake abort reads "received fatal
  alert: InternalError", so matching on the word would have missed the
  exact case this was written for. The needles match rustls' wording.

Both traps are pinned by tests. The classification is a free function
purely so the ORDER can be tested; reintroducing the is_connect-first
bug fails `a_rustls_handshake_abort_is_named_tls_not_connect`, verified
by doing it.

The credential is never included -- only the host, the class, and the
transport chain.

cargo test -p tinymemory-remote: 24 passed (19 + 5 ordering tests)
cargo clippy --all-targets: clean
@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 98e4b33b-574b-45e0-a2cf-c78aea9a8634


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@YellowSnnowmann
YellowSnnowmann merged commit 7235ee9 into tinyhumansai:main Aug 19, 2026
14 checks passed
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