Skip to main content
← Back to list
01Issue
BugShippedSwamp CLIPublic
Assigneesstack72

Relationships

#3642 v3 on S3/GCS: a fresh checkout whose restore from the replica fails reports an empty datastore and exits 0 (found by swamp-uat#715)

Opened by stack72 · 10/19/2026· Shipped 10/19/2026

Background

swamp is reworking its datastore layer (tracking: swamp-club#2865). On a v3 datastore on S3 or GCS (swamp-club#3534), each checkout keeps its own copy of the commit log (commits.db) under .swamp/, restored from the Litestream replica on its first command and refreshed when another checkout has written (swamp-club#3482). Found by swamp-uat#715, which runs the existing datastore UAT on v3 buckets.

The bug

A fresh checkout whose restore from the replica fails reports an empty datastore and exits 0. Read commands print total: 0 with nothing on stderr. Write commands in the same checkout fail correctly ("Restoring the v3 datastore copy failed: …").

Repro (swamp 20261019.165428.0-sha.2ffcdd66, @swamp/gcs-datastore 2026.10.16.5, Litestream 0.5.17). The replica is made unreadable by starting fake-gcs-server without -public-host, so Litestream's XML-API reads get 404:

docker run -d -p 4443:4443 fsouza/fake-gcs-server:1.56.1 \
  -scheme http -port 4443 -external-url http://localhost:4443
curl -X POST http://localhost:4443/storage/v1/b \
  -H 'Content-Type: application/json' -d '{"name":"b"}'
export STORAGE_EMULATOR_HOST=localhost:4443

mkdir a && cd a && swamp init
swamp datastore setup extension @swamp/gcs-datastore --format 3 \
  --config '{"bucket":"b","prefix":"p","apiEndpoint":"http://localhost:4443"}'
# add a command/shell model "shell-echo", then:
swamp model method run shell-echo execute          # exits 0

# a teammate's checkout: everything except .swamp/
mkdir ../b && (cd ../a && tar cf - --exclude=.swamp .) | (cd ../b && tar xf -)
cd ../b
swamp data list shell-echo --json; echo "exit=$?"

Actual: {"groups": [], "total": 0}, exit 0, empty stderr, the same on every run. Expected: the read fails like the write ("Restoring the v3 datastore copy failed: litestream could not restore lineage 000000000001: …"), with a non-zero exit. A checkout that couldn't restore must never present itself as an empty datastore.

Why (src/infrastructure/datastore_v3/litestream/v3_copy.ts, makeV3CopyCurrent): when restoreV3Copy throws in mode: "read", refuseOrSkip("read", …) "reads the copy as it is" and logs at debug. That's the intended offline behaviour for a checkout that already has a copy (stale but real data). A checkout that was never restored has no copy, so "as it is" means empty.

What to do

  1. A read of a copy that was never restored fails with the restore's error (exit 1, the same message and cause as the write path's), instead of reading an empty copy. "Never restored" means no local commits.db, or no copy state naming a lineage. Use whichever localState / readCopyState already tells apart.
  2. A read of an existing copy whose refresh fails keeps reading it, which is the offline behaviour, but warns (not debug) that it shows the checkout's last restored state and why the refresh failed. Say so once per command.
  3. Check the other read entry points that call ensureV3CopyCurrent(…, "read"): serve's read paths, datastore status, data query, the dashboard. Make sure none of them reports an empty datastore for a never-restored copy.
  4. --json mode: the refusal is a normal JSON error envelope with a stable code (e.g. datastore_v3_restore_failed). The warning in (2) goes to stderr as usual.

Tests

Each must fail on main first.

  • Integration (integration/, with the pinned Litestream and a replica the test makes unreadable, e.g. a file replica with its directory removed or unreadable):
    • a fresh checkout's read fails with the restore error;
    • a checkout with an earlier restored copy still reads it, with the warning;
    • a write still fails as today.
  • On buckets: add the same fresh-checkout case to integration/datastore_v3_bucket_test.ts's opt-in S3 run.
  • swamp-uat passes unchanged. swamp-uat#715 adds no journey for this; the in-repo tests carry it.

Severity: S2. A teammate's fresh checkout silently shows no data, with success, which is wrong output a user acts on. Fix it before v3 on buckets is dogfooded (datastore rework Phase 4 I).

02Bog Flow
✓OPEN✓TRIAGED✓IN PROGRESS✓SHIPPED+ 1 MOREASSIGNED+ 5 MOREREVIEW+ 15 MOREPR_MERGED+ 2 MORESESSION_SUMMARIZED

Shipped

10/19/2026, 11:24:04 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack7210/19/2026, 10:14:58 PM

Sign in to post a ripple.