electric-sql / electric-sql/electric-circuits
Shape id reuse after engine restart can append onto stale shape/<id> streams
- Dominant language
- Rust
- Stars
- 30
- Forks
- 6
- PR merge metrics
- No merged PRs in 30d
Description
Found during the production-readiness investigation (vs upstream sync-service).
**Symptom / failure scenario:** shape ids come from an in-memory counter that resets to 1 on restart (`apps/engine/src/engine.rs:291`, `473-475`), while `shape/*` streams from the previous run survive on the durable-streams server. `ensure_stream` is an idempotent PUT (`apps/engine/src/ds.rs:75-92`), so the first post-restart `create_shape` reuses `shape/s1` and appends a fresh backfill **on top of the old run's rows**. A client reading that stream from `offset=-1` sees stale rows merged with the new shape's contents — silent data corruption, no error anywhere.
**Fix direction:** make shape ids collision-free across restarts (persist the counter, or random/uuid ids), and garbage-collect orphaned `shape/*` streams at boot. Both fall out of the shape-catalog/recovery epic, but the id collision is a standalone corruption hazard worth closing first.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Contributor guide
Research direction
Start with the counter and create_shape paths in apps/engine/src/engine.rs:291, 473-475, then inspect ensure_stream in apps/engine/src/ds.rs:75-92. Reproduce a restart with surviving shape/* streams and verify that a new shape cannot append to stale rows; boot recovery should also address orphaned streams if that remains in scope.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- backend, distributed-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100