Skip to content

fix(backend): read apply_control before writes in memory ledger (#9780) - #9783

Merged
kodjima33 merged 1 commit into
mainfrom
watchdog/issue-9780-ledger-read-after-write
Jul 15, 2026
Merged

fix(backend): read apply_control before writes in memory ledger (#9780)#9783
kodjima33 merged 1 commit into
mainfrom
watchdog/issue-9780-ledger-read-after-write

Conversation

@kodjima33

@kodjima33 kodjima33 commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Bug (#9780)

POST /v3/memories returned retryable 503 Service temporarily unavailable for beta/dev Desktop traffic. Sentry group OMI-DESKTOP-2F3: 23 events across 7 users (2026-07-14 → 2026-07-15). Dev Cloud Run logs at the failure times show:

ERROR:routers.memories:Firestore create_memory failed
ReadAfterWriteError: Attempted read after write in a transaction.

The effect is a failed new-memory write (the route returns before post-processing/vector upsert — no successful persistence, no data corruption claimed).

Root cause

_state_head_write_payload() (added in #9739 to preserve canonical state-head fields) reads memory_state/apply_control as a fallback when the state head lacks trusted fields. In both _append_commit_transaction and _append_commit_with_builder_transaction it was invoked inside the final transaction.set(state_ref, ...) — i.e. after projection_writer(transaction) and the commit write had already staged writes. Firestore forbids a transactional read after a write and raises ReadAfterWriteError, which routers/memories.py:create_memory() translates to HTTP 503.

The existing unit tests used a lenient transaction fake that did not enforce Firestore's read-before-write ordering, so they never caught it.

Fix

Build the state-head payload — including the apply_control fallback read — before staging any write, then reuse it for the final state_ref write. Applied to both append paths. #9739's canonical-head preservation is unchanged; no untrusted synthetic head is fabricated.

Durable guard: added a strict transaction fake (_StrictLedgerTransaction / _StrictLedgerDb) that raises ReadAfterWriteError when a get() lands after a set(), plus two regression tests covering the direct and builder append paths through the apply_control fallback.

Verification

  • pytest tests/unit/test_memory_ledger.py29 passed.
  • Confirmed the two new tests fail on pre-fix source (raise ReadAfterWriteError) and pass with the fix — they reproduce the exact prod failure.
  • Broader run test_memory_ledger.py + test_short_term_promotion_control_state.py + test_v3_account_generation_source.py → 42 passed.

Diff: 2 files, +141/-20.

🤖 automated by hourly watchdog; opened for review. This is a live prod bug (503s on /v3/memories); if CI is green it may be auto-merged per the prod-bug gate.

Closes #9780

Review in cubic

POST /v3/memories returned retryable 503s (Sentry OMI-DESKTOP-2F3, 23
events / 7 users) because the legacy memory-ledger transaction performed
a Firestore read after staging writes: `_state_head_write_payload()`
(added in #9739) reads `memory_state/apply_control` as a fallback, but it
was invoked inside `transaction.set(state_ref, ...)` — after
`projection_writer(transaction)` and the commit write. Firestore rejects
this with `ReadAfterWriteError`, which `create_memory()` maps to HTTP 503.

Root cause / durable guard: all transactional reads must precede the
first write. Build the state-head payload (including the apply_control
fallback read) before staging any writes, in both `_append_commit_transaction`
and `_append_commit_with_builder_transaction`, then reuse it for the final
state_ref write. #9739's canonical-head preservation is unchanged.

The existing tests used a lenient transaction fake that ignored ordering,
so they missed the regression. Added a strict transaction fake that raises
ReadAfterWriteError when a get() lands after a set(); the two new tests
fail on the pre-fix code and pass with the fix, covering both the direct
and builder append paths through the apply_control fallback.

Tests: `pytest tests/unit/test_memory_ledger.py` (29 passed); verified the
two new tests fail on pre-fix source (ReadAfterWriteError) and pass after.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kodjima33
kodjima33 merged commit 16fd86e into main Jul 15, 2026
22 of 26 checks passed
@kodjima33
kodjima33 deleted the watchdog/issue-9780-ledger-read-after-write branch July 15, 2026 14:39
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.

Fix /v3/memories 503: Firestore read-after-write in legacy ledger transaction

1 participant