The cancel that did not cancel

posva/pinia-colada

#568 ↗ TypeScript · Vue

The retry plugin scheduled its next attempt with a timer and never subscribed to cancel. Between attempts there is no request to abort — only a timer nobody was clearing.

The defect

The retry plugin’s action subscriber handled extend, remove and fetch. cancel was missing. After a failure it scheduled the next attempt with setTimeout and stored the handle in its retry map — and nothing removed that handle when the user cancelled.

Why it mattered

Between two retries there is no in-flight request, only a pending timer. So cancel aborted an already-empty slot and returned as though it had worked. The guard the timer checks on firing tests whether the entry is active and enabled — and cancelling does neither. The user saw the cancel taken, then a network call fired some delay later and could overwrite the query’s data. A cancel that reports success and does not cancel is worse than one that fails loudly.

The fix

A cancel handler that mirrors the cleanup remove already did, plus a reset of the reactive refs the plugin exposes. The asymmetry is the interesting part: remove destroys the cache entry, so its UI state disappears with it, but a cancelled entry survives — which means isRetrying, retryCount and retryError have to be returned to rest by hand or the UI keeps claiming a retry is pending forever.

Why this repo

Pinia Colada is maintained by Eduardo San Martin Morote, a Vue core team member. Landing a concurrency fix in it means the diagnosis survived review by the person who wrote the surrounding system.