Skip to content

fix(storage): preserve failed writes through interrupted and concurrent recovery - #202

Merged
howethomas merged 4 commits into
mainfrom
thomashowe/con-714-storage-write-failures-are-silently-swallowed-vcons-are
Sep 20, 2026
Merged

howethomas merged 4 commits into
mainfrom
thomashowe/con-714-storage-write-failures-are-silently-swallowed-vcons-are

Conversation

@howethomas

@howethomas howethomas commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

Failed storage recovery could lose its queue entry when the API stopped after popping it, and enqueueing before extending body retention left a crash window. This follows #197 with acknowledgement only after a successful write and atomic body-retention/queue insertion. Longer TTLs and persistent bodies remain unchanged; missing bodies are reported as requiring manual recovery.

Replay runs blocking storage I/O in the threadpool and uses a Redis lock per backend to prevent overlapping snapshots from acknowledging a newer failure for the same UUID. The lock has no expiry; after a process crash an operator must verify all replay workers have stopped before removing it. HTTP sessions now close after use. The recovery and rollout procedure is in docs/storage-recovery.md.

Validation:

  • 31 focused tests passed, including interruption, failed acknowledgement, concurrent replay, real Redis retention, and local HTTP 503/401/retry exhaustion.
  • Common/conserver suite on final merged tree: 278 passed, 2 skipped (isolated RedisJSON); one existing audioop deprecation warning.
  • Independent code review found no remaining required blockers.

Delivery remains at least once and depends on Redis durability, available body retention, and a backend that tolerates repeated UUID writes. Production rollout needs both conserver and API images; no production outage was induced.

Tracks CON-714.

howethomas and others added 4 commits July 30, 2026 17:47
… them

A storage write that failed was logged and forgotten. The vCon never reached
the backend, nothing queued it, and the chain still reported success. On BDS a
~100s outage of one backend silently lost every vCon written during it.

Failures now land on DLQ:storage:<backend> with the vCon's Redis TTL extended
from VCON_REDIS_EXPIRY (1h) to VCON_DLQ_EXPIRY (7d), so the body outlives the
default retention and is still there to replay. Replaying re-attempts only the
storage write: routing through the ingress DLQ instead would re-run the whole
chain, including transcription that already succeeded.

_process_storage still does not raise, so one failing backend does not stop the
others or egress.

Also retries the failures worth retrying in the vcon-mcp client: connection
errors, read timeouts, and 429/502/503/504, with exponential backoff. POST is
included in allowed_methods, which urllib3 excludes by default, because
vcon-mcp upserts on the vCon uuid so a retried create converges rather than
duplicating.

CON-714

Co-authored-by: Cursor <cursoragent@cursor.com>
# Conflicts:
#	api/api.py
#	common/lib/queue.py
#	common/storage/vcon_mcp/__init__.py
#	common/tests/test_storage_dlq.py
#	conserver/main.py
@howethomas
howethomas merged commit f8c15fe into main Sep 20, 2026
1 check passed
@howethomas
howethomas deleted the thomashowe/con-714-storage-write-failures-are-silently-swallowed-vcons-are branch September 20, 2026 22:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant