Retries must be able to mean zero-1 sentinel everywhere.
A spike proved that JS, Python, Java, C# and Elixir already distinguish an explicit 0
from an unset field through their own native mechanisms, so a sentinel there would have
been noise imported from Go's type system. What survives is a narrower, truer change:
one real code fix in Go, one unrelated parity break found in Clojure, and — the part
that matters most — a test in all seven ports, because five of them were correct only
by accident and asserted nowhere. Shipped across seven ports 2026-09-22.Retries is a manifest option, conformance/options_manifest.json).ClientOptions.Retries is documented 0 ⇒ 2 (golang/client.go:50, and the same in every
port). Zero is therefore not expressible: the zero value of the field means "two". For an
HTTP client that default is right — a 429 or a dropped socket is worth riding out. For a
model that is not on a wire, a retry is pure waste: the same local process is launched
again and returns the same thing.
The repo already knows this. CreateInProcessClient installs
OnError ⇒ TierFail internally, and golang/inprocess.go:193 says why in a comment:
This goes through OnError rather than Retries because this port documents
Retries: 0 ⇒ 2, so zero cannot mean zero here without changing shipped behaviour.
That is the admission. The in-process seam needed "no retries", could not say it, and
routed around its own option. A host on CreateClient with a custom transport — the
documented path for a local model (examples/onnx-in-process) — has no such hatch
and no signpost to it.
The draft assumed 0 ⇒ 2 was a seven-port contract. It is not — it is a Go artefact.
Measured, not recalled (spikes/retries-zero/SPIKE.md, each proven with a real client
against a fake failing transport): js/src/client.ts:36,674 (??),
python/src/toolnexus/client.py:545,567 (literal keyword default),
java/.../LlmClient.java:92 (boxed Integer null-check), csharp/.../LlmClient.cs:184
(int? with ??), elixir/.../client.ex:278,356 (struct default plus truthy zero) all
already let an explicit 0 mean zero. Only golang/client.go:521-526 and
classifier.go:779-785 test > 0 / <= 0 on a bare int and cannot.
Had the draft shipped as written, five ports would have gained a -1 spelling that means
nothing in them, to solve a problem they do not have.
The spike also found a defect the draft was not looking for. clojure/'s shipped
default is 0 retries, not 2 (client.cljc:137,520): six ports ride out a 429, Clojure
does not. That is a live, user-visible parity break, and it was invisible precisely because
no port asserted its retry count.
Retries: -1 in Go only, where the bare int leaves no other additive spelling; 0
keeps meaning 2 there for every existing caller. Every other port keeps its native
spelling of an explicit zero. The manifest records these as aliases for one logical option,
exactly as it already does for the HTTP-client option.
Clojure's default is corrected to 2.
And all seven ports get the same pair of tests — zero means exactly one invocation, and the default means three. This is the real content of the change. Five ports needed no code at all; they needed an assertion, because a behaviour that is correct but untested is one refactor away from being wrong, and this repo exists to prevent exactly that drift.
Rejected alternative: documentation only. It leaves the field lying about itself, and the reported failure is a host that read the field and still got 3× the calls.
Considered and deferred: re-defaulting 0 ⇒ 0 with a separate Retries default
constant. It is the honest shape and it is a breaking change to a shipped contract in
seven registries; not worth it for this.
-1 does not reach the backoff arithmetic; no negative sleep, no skipped
first attempt, no unbounded loop. go test -race ./... green.CreateInProcessClient now passes the real spelling and its private
OnError ⇒ TierFail workaround is gone, so the smell this ADR cited as evidence is
removed rather than documented.CHANGELOG.md as a behaviour change in that port.openspec/changes/fix-retries-zero/.Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |