Summary
Electric 1.7.7 deterministically returns an initial HTTP 500 for a cold nested dependency shape. Retrying the same shape succeeds after the dependency snapshots finish.
Minimal topology
assets
WHERE id NOT IN (
SELECT asset_id
FROM container_rels
WHERE asset_id IS NOT NULL
AND asset_id NOT IN (
SELECT asset_id
FROM container_rels
WHERE asset_id IS NOT NULL
AND container_id IN (
SELECT id FROM containers WHERE <per-user access predicate>
)
)
)
The equivalent historical positive-IN topology did not fail in the matched run.
Controlled results on 1.7.7
- Concurrency 1: one current nested shape returned 500 after 5.191 seconds; retry returned 200 after 2.966 seconds and reached
0_inf.
- Bounded concurrency 4: four current nested shapes produced exactly four first-request 500s; all recovered on retry.
- All five recovered clients had valid handles, reached up-to-date, returned the expected accessible row set, and excluded the inaccessible probe row.
- Five matched historical positive-
IN shapes returned 200 on their first request.
- Service logs contained five shape removals caused by
GenServer.call(intermediate_materializer, :subscribe, 5000) timing out and five outer await_snapshot_start(..., 45000) request failures.
- No restart, OOM, missing-materializer, Stack-not-ready, snapshot-failure, or iodata signature occurred in the bounded window.
Expected
A single cold nested shape waits for dependency materializers and completes without an externally visible 500.
Actual
The outer consumer times out after five seconds while subscribing to the intermediate dependency materializer. Electric removes the outer shape and returns 500. A later client retry succeeds.
Relevant source
In 1.7.7:
packages/sync-service/lib/electric/shapes/consumer/materializer.ex: subscribe/1 uses the default five-second GenServer.call timeout.
packages/sync-service/lib/electric/shapes/consumer.ex: all_materializers_alive?/1 calls Materializer.subscribe/1 during outer consumer initialization.
A minimal read-only client harness can be provided. It creates distinct cold shape keys with a no-op predicate nonce and records status, latency, handle, offset, up-to-date state, and row-set correctness. It does not require schema or data mutation.
Retry is a client mitigation, not a resolution: every cold current-topology request in the matched run still exposed an initial 500.
Summary
Electric 1.7.7 deterministically returns an initial HTTP 500 for a cold nested dependency shape. Retrying the same shape succeeds after the dependency snapshots finish.
Minimal topology
The equivalent historical positive-
INtopology did not fail in the matched run.Controlled results on 1.7.7
0_inf.INshapes returned 200 on their first request.GenServer.call(intermediate_materializer, :subscribe, 5000)timing out and five outerawait_snapshot_start(..., 45000)request failures.Expected
A single cold nested shape waits for dependency materializers and completes without an externally visible 500.
Actual
The outer consumer times out after five seconds while subscribing to the intermediate dependency materializer. Electric removes the outer shape and returns 500. A later client retry succeeds.
Relevant source
In 1.7.7:
packages/sync-service/lib/electric/shapes/consumer/materializer.ex:subscribe/1uses the default five-secondGenServer.calltimeout.packages/sync-service/lib/electric/shapes/consumer.ex:all_materializers_alive?/1callsMaterializer.subscribe/1during outer consumer initialization.A minimal read-only client harness can be provided. It creates distinct cold shape keys with a no-op predicate nonce and records status, latency, handle, offset, up-to-date state, and row-set correctness. It does not require schema or data mutation.
Retry is a client mitigation, not a resolution: every cold current-topology request in the matched run still exposed an initial 500.