Shipwreck
A chaos-testing CLI that checks whether a web app really recovers from network faults.
Proxies frontend-to-backend traffic, injects controlled network faults and verifies that the client recovers. Its assertion engine catches duplicate non-idempotent requests caused by timeout-and-retry races.
- Node.js
- TypeScript
- CLI
- Fault injection
The problem
Most web apps are only ever tested on a healthy network. In production, requests time out, connections drop and clients retry, and those retries are where expensive bugs hide. A POST that timed out on the client may still have reached the server. If the client retries and the endpoint isn't idempotent, that's a second order or a second charge.
Fault-injection tools can make requests fail, but they usually stop there. They show that something broke, not whether the application handled it correctly.
Approach
Shipwreck is a command-line tool that sits between the frontend and the backend as a proxy. It forwards real traffic, injects controlled network faults and records what happens.
A request-fingerprinting and assertion engine then checks the behaviour automatically: did the client recover, and did a timeout-and-retry race send the same non-idempotent request twice? That moves fault-injection testing from detecting failures to verifying behaviour.
How it works
Frontend
The app under test, pointed at Shipwreck instead of the real API.
Proxy and fault injection
Forwards traffic and injects controlled network faults, such as timeouts.
Backend
Receives the traffic, including any retries the client sends.
Request fingerprinting
Matches repeated attempts of the same logical request.
Assertion engine
Flags duplicate non-idempotent requests and checks that the client recovered.
Key decisions
A proxy, not an SDK
Sitting on the network path means the app under test needs no code changes, and injected faults look like real network failures to both the client and the server.
Assert behaviour, not just failure
A test should fail when a retry duplicates a write, not merely when a request errors. The assertion engine checks what correct recovery looks like automatically, without hand-written test scripts.
Fingerprint requests to spot duplicates
Retries of one logical operation are matched by fingerprint, so a duplicate non-idempotent request is caught even when each attempt looks successful on its own.
Observability
Shipwreck shows request traces and retry behaviour in a command-line dashboard, so you can see which request was retried, when, and what the backend received.
Results
It turns resilience from an assumption into a test. The bug it targets is one I designed against in payment workflows at work: a timeout-and-retry race that turns one intended request into two.