Library / SDK
connor4312/cockatiel avatar
connor4312/cockatiel

cockatiel makes retries, breakers and bulkheads composable, and decides what counts as a fault

🐦 A resilience and fault-handling library. Supports Backoffs, Retries, Circuit Breakers, Timeouts, Bulkhead Isolation, and Fallbacks.

1,833 stars63 forksTypeScriptMIT

At a glance

What is it?
cockatiel is a dependency free TypeScript library for transient fault handling that offers retry, circuit breaker, timeout, bulkhead isolation and fallback policies behind one IPolicy interface. Its defining choice is that no thrown error is treated as a failure until a handler says so, which is the opposite of most retry helpers.
Who is it for?
cockatiel is a good fit when you already use AbortSignal and want one policy vocabulary shared across services, and a poor fit for a codebase on an older Node release, since package.json declares an engine requirement of Node 22 or later and the repository metadata and the tagged release list disagree about the current version.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 9 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • connor4312/cockatiel1,833 ★ · +3
Star history
Repository2026-10-162026-11-07
connor4312/cockatiel1.8k1.8k

3 Stars in the last 22 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Nothing fails until you name the errors that should

The design decision that separates cockatiel from a retry helper with a boolean flag is stated in the IPolicy documentation. A retry policy fires onSuccess once even if the request took multiple retries, and onFailure is only called if a handled error is thrown. As the README puts it, Cockatiel will not assume all thrown errors are actually failures unless you tell it to.

The reason given is practical rather than philosophical. An application throws errors when a user submits invalid input, and triggering fault handling for that reason would not be desirable. So the policy is the thing that decides, not the call site.

That decision is expressed through a set of combinators rather than a single option. handleAll catches everything. handleType(ctor) narrows to one error class, handleWhen(filter) narrows by predicate, and there are result-oriented variants, handleResultType and handleWhenResult, for code that returns a failure value instead of throwing. Every combinator also exists as a method on an existing policy, spelled orType, orWhen, orResultType and orWhenResult, so a default policy can be widened locally at one call site without redefining the shared instance.

Policies compose with wrap(...policies), which is what makes the library more than a retry loop.

The library installs as an ordinary npm dependency:

bash
npm install --save cockatiel

Retry takes a backoff strategy object, and there are four of them

retry(policy, options) is the entry point, and the shape of the example makes the defaults visible. A policy that tries three times with a randomized exponential backoff is written as retry(handleAll, { maxAttempts: 3, backoff: new ExponentialBackoff() }).

Backoff is a strategy object rather than a number of options, and four implementations are documented. ConstantBackoff waits the same amount every time. ExponentialBackoff grows the delay, and the example's comment describes it as randomized. IterableBackoff takes its delays from an iterable, which is how you express a schedule you already know. DelegateBackoff delegates the decision to a function.

Retry also exposes the callbacks you would otherwise scatter through your own loop. onRetry fires before each attempt, onSuccess fires when the operation finally succeeds, onFailure fires when it does not, and onGiveUp fires when the policy has decided to stop. For an operation that takes several attempts to succeed, onSuccess still fires exactly once, which matters if you are using it to record a latency metric rather than an attempt count.

There is also retry.dangerouslyUnref(), a name that deserves attention before use: it stops the retry timer from holding the Node event loop open.

Circuit breakers come in three flavours and expose their state

circuitBreaker(policy, options) requires halfOpenAfter and a breaker, with halfOpenSampling and initialState also available. The documented example opens the half open state after ten seconds when five failures occur in a row, using ConsecutiveBreaker(5).

Three breaker strategies are documented and they answer different questions. ConsecutiveBreaker counts failures in a row, so any success resets it. CountBreaker counts within a window regardless of order. SamplingBreaker takes a proportion of recent results, which is what you want when some requests fail normally.

State is observable rather than hidden. breaker.state reports the current state, breaker.toJSON() serialises it, and four lifecycle callbacks exist: onBreak, onReset, onHalfOpen and onStateChange. breaker.isolate() is the odd one out and is the closest thing to a bulkhead in the breaker itself.

The combined pattern from the README is one line, wrap(retryPolicy, circuitBreakerPolicy), and the point of combining them is ordering. Retrying inside a breaker means the breaker counts one failure per logical call rather than per attempt, which is the behaviour you want when the downstream is down rather than flaky.

Timeout, bulkhead and fallback share execute and take an AbortSignal

Every policy implements the same execute signature, execute(fn[, signal]), which is what allows wrap to accept an arbitrary list. The ContextType is constrained to an object carrying a signal: AbortSignal, so cancellation is part of the contract rather than an extra.

timeout(duration, strategy) takes a strategy as a second argument, which the README documents as such without enumerating the built-in values in the section shown. Its callbacks are onTimeout, onSuccess and onFailure, and timeout.dangerouslyUnref() mirrors the retry method of the same name.

bulkhead(limit[, queue]) is the isolation piece, and it exposes two counters that answer the operational question directly: executionSlots for in flight work and queueSlots for work waiting. Callbacks are onReject, onSuccess and onFailure, so a caller can distinguish being throttled from being failed.

fallback(policy, valueOrFactory) is the odd member of the set, since its policy argument is a predicate deciding whether to fall back rather than a thing to compose around, and its second argument is either a fixed value or a factory. Its callbacks are only onSuccess and onFailure, because there is no retry attempt to report.

A Node 22 floor, no dependencies, and a version story worth checking

The package metadata is where the engineering constraints live. cockatiel declares engines node >=22, ships type module with main at dist/index.js, and sets sideEffects to false so a bundler can tree shake unused policies. The README carries a no dependencies badge, and devDependencies are all test and tooling packages rather than runtime ones.

Version numbers need care here. package.json reads 4.0.0, while the release list for the repository shows a single tagged release, v2.0.0 from September 2020. Those two facts are both in the repository record, and the gap between them is why anyone installing should confirm the version they resolve rather than assuming either number.

Development still happens. The last push was on 2026-10-08, and the repository holds src/, a changelog.md, a mocha.opts file and .mocharc.cjs, so the test runner is mocha with TypeScript compiled first. The test script is worth noting for contributors because it runs three checks in sequence: the unit tests, a prettier check over src and markdown, and remark over the readme.

Where cockatiel sits against Polly and hand rolled loops

The lineage is explicit: the author describes Polly as the.NET one stop shop for fault handling that he missed having for JavaScript, after getting tired of copy pasting retry logic between projects. cockatiel is that library for Node, and the README recommends reading the Polly wiki for the pattern mechanics, which is an admission that the concepts came from there and the documentation does not re-explain them.

Against a hand rolled retry loop the difference is not capability but state. A loop inside one function cannot be shared, cannot be observed, and resets every call. A policy is an object you can pass into wrap, attach callbacks to, and share across a service boundary.

Against other JavaScript resilience libraries the distinguishing feature is the handled error model plus composability of arbitrary policy lists. Most retry packages treat any rejection as retryable or take a single predicate for one function. cockatiel makes the predicate a property of the policy, which means the same policy object can be composed with a breaker, a timeout and a bulkhead without restating the error filter at each layer.

The cost is that the API surface is wide. The README's table of contents lists more than sixty members across policies, events and callbacks, and while each is small, there is no single object that does everything, so you assemble what you need.

Licensing is MIT, so vendoring requires retaining the copyright and licence notices.

Editorial conclusion

cockatiel is a good fit when you already use AbortSignal and want one policy vocabulary shared across services, and a poor fit for a codebase on an older Node release, since package.json declares an engine requirement of Node 22 or later and the repository metadata and the tagged release list disagree about the current version. Verify first which version you are actually installing by checking the version field of the published package, because the release list shows only v2.0.0 from 2020 while package.json reads 4.0.0.

Frequently asked questions

How do I install cockatiel?

The README gives npm install --save cockatiel. The package declares an engine requirement of Node 22 or later and ships as an ES module with no runtime dependencies.

How do I retry only on specific errors with cockatiel?

Pass a handler instead of handleAll: handleType(ctor) filters by error class and handleWhen(filter) filters by a predicate. Nothing is treated as a failure unless a handler matches it.

How do I combine a retry policy with a circuit breaker in cockatiel?

Use wrap(retryPolicy, circuitBreakerPolicy). The README example retries three times through a breaker that opens for ten seconds after five consecutive failures.

What is dangerouslyUnref used for in cockatiel?

It exists on retry and on timeout policies and stops the policy's timer from holding the Node event loop open, which matters when a process would otherwise wait on a pending retry.

How do I limit concurrency with cockatiel?

Use bulkhead(limit[, queue]). It exposes executionSlots for in flight work and queueSlots for waiting work, with an onReject callback fired when work is refused.

Official sources

  1. connor4312/cockatiel on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/connor4312-cockatiel.svg)](https://hysenlabs.com/projects/connor4312-cockatiel)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/connor4312-cockatiel.svg)](https://hysenlabs.com/projects/connor4312-cockatiel)

Was this useful?