electric-sql / electric-sql/electric
Bug: Client treats 'offset out of bounds' (400) as fatal — should auto-recover like 409
- Dominant language
- TypeScript
- Stars
- 10.4k
- Forks
- 375
- Avg merge
- 3d 1h
- Merged PRs (30d)
- 18
Description
## Problem
When the Electric server returns a 400 with `{"errors": {"offset": ["out of bounds for this shape"]}}`, the TypeScript client treats it as an unrecoverable error. The error is thrown from `#requestShape`, caught by `#start`, and either passed to `onError` or sent to error subscribers — terminating the stream.
However, "offset out of bounds" is a **recoverable** situation. The client's offset is ahead of (or no longer within) the server's known range for the shape. The correct recovery is the same as a 409: reset the shape and refetch from scratch.
## Current behavior
`packages/typescript-client/src/client.ts` line ~883:
```typescript
} else {
// errors that have reached this point are not actionable without
// additional user input, such as 400s or failures to read the
// body of a response, so we exit the loop and let #start handle it
throw e
}
```
All non-409 `FetchError`s — including "offset out of bounds" — hit this branch and are thrown as fatal.
## Expected behavior
When the client receives a 400 with `offset: ["out of bounds for this shape"]`, it should:
1. Reset the shape state (clear handle, offset back to `-1`)
2. Refetch from scratch — same as 409 recovery
This is equivalent to saying "I don't know where I am in this shape's log anymore, start over."
## Customer impact
A customer running 20+ shapes saw this error after applying a template that created many rows in a single transaction. The stream died and did not recover. See #3898 for the likely server-side root cause.
## Proposed fix
In `#requestShape`'s error handling, detect the "offset out of bounds" 400 and treat it like a 409:
```typescript
if (e.status === 400 && e.json?.errors?.offset?.includes('out of bounds for this shape')) {
this.#reset()
return this.#requestShape()
}
```
Alternatively, a more general approach: treat any 400 with an `offset` error as a signal to refetch.
## Related
- #3900 — Server-side: Chunk boundary race exposes mid-transaction offsets
- #3896 — Backoff defaults improvement (reduces retry storm impact)
Contributor guide
Assessment
This issue has not been assessed yet.