Build1 distinct publisher3 min readUpdated
A shared circuit breaker looks like a three-state machine until a stale decision lands on a fresh half-open period and kills a recovery it never observed.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
breakwater, a Node.js resilience toolkit covering retry, circuit breaker, timeout, bulkhead, rate limiting and stale-while-open caching, has shipped 1.0.0, and its headline feature is a circuit breaker whose state is shared across every instance of a service [1][2]. According to its author, the release was gated by a correctness bug with a general lesson: once state is shared, closed/open/half-open is not a sufficient state space, because half-open entered twice is not the same state [4][8].
The intended behaviour is straightforward. One instance sees the outage and trips the breaker, the others fail fast without rediscovering the outage on their own users, and when the cooldown elapses exactly one instance probes the recovering dependency while the rest keep waiting [3].
In a single process, none of the transitions need protection: JavaScript is single-threaded, and the code between two awaits cannot be interrupted [5]. Shared across N instances, every transition becomes a compare-and-set, which the first store interface expressed as `transition(name, from, to): boolean`, swapping only if the state was still `from`, implemented atomically in Redis with a Lua script [6].
The author's failing sequence: a probe fails and the instance decides to reopen; that decision spends a few milliseconds in a Lua round trip; meanwhile another probe succeeds, reaches the majority, closes the circuit, traffic resumes, fails again, reopens, waits out the cooldown and re-enters half-open. The first instance's swap then lands, asks whether the state is still half-open, finds that it is, and succeeds, killing a recovery it never observed on the basis of a decision belonging to a period that ended three transitions earlier [7]. This is ABA, disguised by the fact that the states have names; half-open is a label the circuit wears repeatedly, not an identity [8].
The first attempt at a fix is the part worth studying. The race predated the distributed store, since the in-process breaker had the same window whenever a custom store was async [9], so the author patched it: after a successful swap, check whether the period had flipped and, if so, swap the state back, with a comment describing the behaviour as best effort until stores could fence the CAS with a generation [10]. His own verdict is that two swaps are not one swap, another instance sees the wrong state in between, the compensating swap can itself fail, and he shipped it only because the alternative was redesigning the store contract [11].
The eventual fix makes the generation explicit: a monotonic fence token minted on every successful transition, `readState` returning state, fence and optional openedAt, and `compareAndSet(name, from, to, fence)` returning ok plus a snapshot [12]. A swap lands only if the state is still `from` and nothing has transitioned since the fence was read, so a stale decision carrying fence 7 against a store holding fence 10 is refused atomically inside the same script [13] - three transitions of drift, visible as arithmetic rather than as a label comparison [1]. Both compensating transitions were deleted [14].
Two consequences fell out. A lost race now returns the current snapshot, where it previously cost a second round trip to learn what had happened [15]. And openedAt now lives in Redis, stamped from the server clock, instead of each instance counting the cooldown from when it first noticed the trip, which had left instances disagreeing about when probing was allowed and let the earliest noticer probe too soon [16].
What to watch is the cost of the premise: Redis is now on the path of every protected call [17]. The author's answer is that no store method ever rejects, and that an unreachable Redis falls back to what the instance already has [17]; the supplied account breaks off there, so the thing to verify in the shipped code is whether the exactly-one-prober guarantee [3] survives a fallback that answers from local state.
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
breakwater is a resilience toolkit for Node.js covering retry, circuit breaker, timeout, bulkhead, rate limiting and stale-while-open caching, composable, with observability built in.
breakwater has released version 1.0.0, and its headline feature is a circuit breaker whose state is shared across every instance of a service.
Intended behaviour of the shared breaker: one instance sees the outage and trips the breaker, other instances fail fast immediately without each discovering the outage on their own users, and when the cooldown elapses exactly one instance probes the recovering dependency while the rest keep waiting.
A circuit breaker is described as a tiny state machine with three states: closed, open and half-open.
In a single process the author protects state transitions with nothing at all, because JavaScript is single-threaded and the code between two awaits cannot be interrupted.
Sharing state across N instances makes every transition a compare-and-set; the original store interface was transition(name, from, to): boolean, swapping only if the state is still `from`, made atomic in Redis via a Lua script.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Detailed but single-source and self-reported
The cluster rests entirely on one maintainer-authored post. Its internal evidence is unusually specific — before/after store signatures, a step-by-step race sequence, a verbatim code comment, a fence-7-vs-fence-10 rejection, and a numeric failure measurement — which is well above the norm for a release write-up. But nothing is independently reproduced or reviewed, the test environment for the 73-second measurement is unspecified, no repository, test suite, or issue is cited, and the supplied text breaks off mid-remedy.
Self-announced 1.0.0, no external usage evidence
The only adoption-relevant fact is the maintainer's own announcement that breakwater hit 1.0.0. The supplied material contains no downloads, dependents, production deployments, contributors, or third-party evaluations, and the single external touchpoint mentioned is an unnamed reviewer who asked a question. That supports a real but very early release signal and nothing more.
Slightly overstated framing, candid body
The framing overreaches modestly: an unquantified superlative that no Node library did shared breaker state well, plus a 1.0 pitch that 'exactly one instance probes', both resting on the author's word with zero adoption evidence. The body pulls hard in the other direction — it documents two of its own wrong designs and reports that the library's headline safety property silently hung protected calls for 73 seconds under the configuration its own docs recommended. Net result is only marginally overstated relative to the evidence.
Maintainer promoting own 1.0 release
The sole source is written by the library's own author on a developer-blogging platform, timed to a 1.0.0 launch, and includes competitive positioning against unnamed Node alternatives — a direct promotional interest in adoption and credibility. The incentive is partially offset by candid disclosure of two design failures and a self-damaging measurement, but not removed: no third-party or editorially independent account exists in the cluster.
Technically coherent, single-publisher, partly truncated
The engineering narrative is internally consistent and specific enough to be actionable, so confidence in what the author built and why is moderate. Confidence is capped by there being exactly one publisher, one interested author, no reproduction of the ABA race or the timing measurement, no adoption evidence, and a supplied text that ends mid-sentence while documenting the timeout mitigation.
build
1,254 dead mutants, a 100% score, and a payment charged twice1 distinct publisher
build
The queue failure that costs you is silent, and it is what BullMQ tutorials skip1 distinct publisher
build
Allow-list the closed set, block-list the open one: 193 thin geo pages, one gate1 distinct publisher
build
A GAN beauty filter is a device budget allocation, not a feature toggle1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 15, 2026