You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: make catch-up a guarantee rather than a hope
Three separate things stood between "a replay was requested" and "every
missed request was recovered". Each was measured against a live project
rather than reasoned about, and each was wrong.
The filter matched only half the cases. The two disconnect regimes look
different, and only one was covered:
tunnel never existed events_count 0, ignored_count 1
tunnel connected, then killed events_count 0, ignored_count 0
The second is the hard crash. It matches neither `ignored_count >= 1`
nor `cli_events_count: 0` — measured directly, that field is not
populated when no CLI event was ever created, so filtering on it excludes
exactly the case catch-up is for. The query now keys on `events_count: 0`
alone, which is the true signature of a request that produced no
delivery. Live, the replay went from matching ~0 requests to 3 of 3.
The window did not survive a crash, fixed in the previous commit.
The outcome was assumed. The replay call returns as soon as the batch is
accepted, so `estimated_count` is a plan and not a result. The plugin now
polls the batch until Hookdeck reports it finished, and logs `N of M
request(s) replayed`; a shortfall warns and names it, and a batch that
does not finish inside the wait is reported as unknown rather than as
success. The wait is bounded, because this runs when a tunnel reconnects
and must not hold recovery open indefinitely.
One approach was tried and abandoned, and the reason is worth keeping: a
post-replay pass that re-read the window for requests still showing no
event. It can never pass. A replay re-ingests each request as a NEW
request with new events, so the original stays at events_count 0 for
ever — the check reported recovered requests as stranded, and no amount
of waiting would have changed that. The batch's own counts are the only
evidence available.
The honest limit is retention: 3 days on free, 7 on Team, 30 on Growth.
Beyond it the request is gone at the source and nothing reaches it.
669 tests, and the live suite is now 14/14.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/durability.md
+21Lines changed: 21 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -32,6 +32,27 @@ So while the transport is running the plugin records a liveness marker every `ca
32
32
33
33
Over-shooting that window by up to one heartbeat is harmless: the catch-up query matches only requests that produced no event at all, so anything that did deliver is excluded by construction.
34
34
35
+
## What catch-up guarantees
36
+
37
+
Within Hookdeck's retention, catch-up recovers every request that arrived during an outage and produced no delivery. Three things make that a guarantee rather than a hope, and each was wrong at some point:
38
+
39
+
**The filter matches every way a request can be stranded.** Measured against a live project, the two disconnect regimes look different:
40
+
41
+
||`events_count`|`ignored_count`|
42
+
|---|---|---|
43
+
| The tunnel never existed | 0 | 1 |
44
+
| The tunnel connected, then the process was killed | 0 |**0**|
45
+
46
+
The second is the hard-crash case. It matches neither `ignored_count >= 1` nor `cli_events_count: 0` — that field is not populated when no CLI event was ever created — so a filter using either excludes precisely the case catch-up exists for. The query keys on `events_count: 0` alone.
47
+
48
+
**The outage window survives a crash.** See [above](#a-crash-that-never-ran-its-shutdown).
49
+
50
+
**The result is confirmed, not assumed.** The replay call returns as soon as the batch is accepted, so its `estimated_count` is a plan. The plugin polls the batch until Hookdeck reports it finished and then logs `N of M request(s) replayed`. A shortfall is a warning naming it; a batch that does not finish within the wait is reported as unknown rather than as success.
51
+
52
+
What it cannot do is recover anything Hookdeck has already aged out — 3 days on free, 7 on Team, 30 on Growth. Beyond that the request is gone at the source, and no filter or replay reaches it.
53
+
54
+
> Do not try to verify recovery by re-reading the original requests. A replay re-ingests each one as a **new** request with new events, so the original stays at `events_count: 0` for ever. Checking it will always report a stranded request that was in fact recovered.
55
+
35
56
## Malformed bodies never reach the plugin
36
57
37
58
Verified end to end: Hookdeck rejects an unparseable JSON body **at the edge**, answering the sender `400` with `rejection_cause: UNPARSABLE_JSON` and creating no event. The plugin's own `malformed_json` handling is therefore defence in depth rather than a path real traffic takes — it covers a body that survives the edge and fails here, such as one that is valid JSON but not valid UTF-8.
0 commit comments