The binary make install builds retries a refused or unresolvable connection (closes #206) #207

Merged
clawbot merged 1 commits from issue-206-bun-fetch-errors into next 2026-10-16 04:56:21 +02:00
Collaborator

For #206.

The ~/bin/quak that make install builds runs under bun. Bun's fetch rejects with a plain Error that has no cause and a code of bun's own, so isRetryable found neither Node's TypeError nor a known errno. Running the built binary against listeners on 127.0.0.1 and a name under .invalid gave:

  • refused connection, or failed TLS handshake: ConnectionRefused
  • name that does not resolve: ConnectionRefused or FailedToOpenSocket
  • reset, or closed before the response ended: ECONNRESET
  • deadline: TimeoutError

The two new codes join TRANSPORT_CODES in src/retry.ts, so they are retried, and classify in src/backup.ts calls them transient. They are not in CONNECT_CODES, so a POST or PUT is not replayed on them. When a connection bun kept open from an earlier request closes before any answer, bun sends the request again on a new connection, and if that one fails it gives one of these codes although the server may already have the request.

classify also records an error named TimeoutError as transient, as isRetryable retries it. quak's download deadline aborts with one whose message says only that the download stalled, so a stalled download was recorded as unknown.

Not visible in the diff: the API origin is fixed, so the binary reached the local listeners only through HTTPS_PROXY.
Unverified: that bun gives FailedToOpenSocket when it resends a request comes from reading bun's behaviour, not from a run.
Not changed: a certificate bun rejects (DEPTH_ZERO_SELF_SIGNED_CERT) is not retried, and the README says so.

Model: opus-5-5

For https://git.eeqj.de/sneak/quak/issues/206. The `~/bin/quak` that `make install` builds runs under bun. Bun's `fetch` rejects with a plain `Error` that has no `cause` and a code of bun's own, so `isRetryable` found neither Node's `TypeError` nor a known errno. Running the built binary against listeners on 127.0.0.1 and a name under `.invalid` gave: - refused connection, or failed TLS handshake: `ConnectionRefused` - name that does not resolve: `ConnectionRefused` or `FailedToOpenSocket` - reset, or closed before the response ended: `ECONNRESET` - deadline: `TimeoutError` The two new codes join `TRANSPORT_CODES` in `src/retry.ts`, so they are retried, and `classify` in `src/backup.ts` calls them transient. They are not in `CONNECT_CODES`, so a `POST` or `PUT` is not replayed on them. When a connection bun kept open from an earlier request closes before any answer, bun sends the request again on a new connection, and if that one fails it gives one of these codes although the server may already have the request. `classify` also records an error named `TimeoutError` as transient, as `isRetryable` retries it. quak's download deadline aborts with one whose message says only that the download stalled, so a stalled download was recorded as `unknown`. Not visible in the diff: the API origin is fixed, so the binary reached the local listeners only through `HTTPS_PROXY`. Unverified: that bun gives `FailedToOpenSocket` when it resends a request comes from reading bun's behaviour, not from a run. Not changed: a certificate bun rejects (`DEPTH_ZERO_SELF_SIGNED_CERT`) is not retried, and the README says so. Model: opus-5-5
clawbot added the needs-review label 2026-10-16 03:53:51 +02:00
clawbot self-assigned this 2026-10-16 03:53:56 +02:00
Author
Collaborator
  1. Bun gives ConnectionRefused after the server already has the request, so this PR lets a POST or PUT be sent twice (CONNECT_CODES and isSafeToReplay in src/retry.ts). Bun may send a request on a connection it kept open from an earlier request. If that connection closes before any answer, bun sends the request again on a new connection, and when that new connection is refused, fetch rejects with ConnectionRefused. Under bun 1.3.13, quak's own postJSON sent /users/two-factor/verify to a local server twice this way: once on the reused connection, and once more when it was replayed. Bun reports FailedToOpenSocket from that same new connection, so it carries the same doubt. That one I read in bun's behaviour and did not reproduce. Acceptable: neither bun code is in CONNECT_CODES. Both stay in TRANSPORT_CODES, so they are still retried for idempotent calls and recorded as transient. The tests in test/retry/retry.test.ts expect isSafeToReplay to be false for both. The comments on CONNECT_CODES and isSafeToReplay, the README paragraph on replaying requests and the TODO.md entry say that a POST or PUT is not replayed on them, and why.

  2. A stalled download is still recorded as unknown rather than transient (classify in src/backup.ts). The issue asks the backup to record a timeout as transient. quak's own download deadline aborts with a TimeoutError whose message, "download stalled: no bytes received for ... ms", contains none of the words classify looks for, under Node and bun alike. Acceptable: classify records an error named TimeoutError as transient, as isRetryable already retries it. A test in test/cli/backup-end-to-end.test.ts stalls a download and expects transient in failures.json. The PR body no longer lists the stall as not changed.

Model: opus-5-5

1. Bun gives `ConnectionRefused` after the server already has the request, so this PR lets a `POST` or `PUT` be sent twice (`CONNECT_CODES` and `isSafeToReplay` in `src/retry.ts`). Bun may send a request on a connection it kept open from an earlier request. If that connection closes before any answer, bun sends the request again on a new connection, and when that new connection is refused, `fetch` rejects with `ConnectionRefused`. Under bun 1.3.13, quak's own `postJSON` sent `/users/two-factor/verify` to a local server twice this way: once on the reused connection, and once more when it was replayed. Bun reports `FailedToOpenSocket` from that same new connection, so it carries the same doubt. That one I read in bun's behaviour and did not reproduce. Acceptable: neither bun code is in `CONNECT_CODES`. Both stay in `TRANSPORT_CODES`, so they are still retried for idempotent calls and recorded as `transient`. The tests in `test/retry/retry.test.ts` expect `isSafeToReplay` to be false for both. The comments on `CONNECT_CODES` and `isSafeToReplay`, the README paragraph on replaying requests and the `TODO.md` entry say that a `POST` or `PUT` is not replayed on them, and why. 2. A stalled download is still recorded as `unknown` rather than `transient` (`classify` in `src/backup.ts`). The issue asks the backup to record a timeout as `transient`. quak's own download deadline aborts with a `TimeoutError` whose message, "download stalled: no bytes received for ... ms", contains none of the words `classify` looks for, under Node and bun alike. Acceptable: `classify` records an error named `TimeoutError` as `transient`, as `isRetryable` already retries it. A test in `test/cli/backup-end-to-end.test.ts` stalls a download and expects `transient` in `failures.json`. The PR body no longer lists the stall as not changed. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-16 04:07:22 +02:00
clawbot added 1 commit 2026-10-16 04:32:51 +02:00
Bun's `fetch`, which `~/bin/quak` runs under, rejects with a plain `Error`
that has no `cause` and a code of bun's own. Running the built binary against
local listeners gave `ConnectionRefused` for a refused connection and a failed
TLS handshake, and `ConnectionRefused` or `FailedToOpenSocket` for a name that
does not resolve. Both join `TRANSPORT_CODES` in `src/retry.ts`, so they are
retried and called transient. They stay out of `CONNECT_CODES`, so a `POST` or
`PUT` is not replayed on them, because bun also gives them when it resends a
request on a new connection. `classify` in `src/backup.ts` records a
`TimeoutError` as transient, so a stalled download is no longer `unknown`.

Not changed: a certificate bun rejects is not retried.

Model: opus-5-5
clawbot force-pushed issue-206-bun-fetch-errors from 4c9a42db49 to 0fb212736d 2026-10-16 04:32:51 +02:00 Compare
Author
Collaborator

Reworked for #207 (comment), now one commit at 0fb2127 on current next.

  1. Fixed as suggested: both bun codes are out of CONNECT_CODES and still in TRANSPORT_CODES, the tests expect no replay for either, and the two comments, the README, TODO.md and the PR body say why.
  2. Fixed as suggested: classify records a TimeoutError as transient, and a new test in test/cli/backup-end-to-end.test.ts stalls the download of toast.mp4 and expects transient in failures.json. The PR body no longer lists the stall as not changed.

The PR body is down to about 250 words.

Model: opus-5-5

Reworked for https://git.eeqj.de/sneak/quak/pulls/207#issuecomment-144583, now one commit at `0fb2127` on current `next`. 1. Fixed as suggested: both bun codes are out of `CONNECT_CODES` and still in `TRANSPORT_CODES`, the tests expect no replay for either, and the two comments, the README, `TODO.md` and the PR body say why. 2. Fixed as suggested: `classify` records a `TimeoutError` as transient, and a new test in `test/cli/backup-end-to-end.test.ts` stalls the download of toast.mp4 and expects `transient` in `failures.json`. The PR body no longer lists the stall as not changed. The PR body is down to about 250 words. Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-16 04:33:11 +02:00
Author
Collaborator

Review passed.

Model: opus-5-5

Review passed. Model: opus-5-5
clawbot merged commit 20410f8f71 into next 2026-10-16 04:56:21 +02:00
clawbot deleted branch issue-206-bun-fetch-errors 2026-10-16 04:56:21 +02:00
Sign in to join this conversation.