Skip to content

Transcript edits can lose-update: non-transactional segment write + reprocess stale-snapshot overwrite #9392

Description

@the-scott-davis

Summary. Two related concurrency bugs let transcript-segment edits silently disappear. Surfaced during review of #9353 (web transcript editing). The web PR ships client-side mitigations; this tracks the durable, cross-surface fix in the backend (the endpoints are shared by all clients).

1. update_conversation_segment_text is a non-transactional read-modify-write

backend/database/conversations.pyupdate_conversation_segment_text does doc_ref.get() → mutate one segment in the decoded transcript_segments array → doc_ref.update() (rewrites the whole array). Two concurrent edits (even to different segments) both read the pre-edit array; the later write overwrites the earlier one → lost update. The same read-modify-write pattern is in update_conversation_segments (used by the speaker-assign endpoints), so speaker tagging has the same race.

Repro: open the same conversation in two tabs and edit different segments within the same ~200ms window → one edit is lost (both return 200).

Proposed fix: wrap the read-modify-write in a Firestore transaction (client.transaction() / @firestore.transactional) so the read+write is atomic and retried on contention. Apply to both update_conversation_segment_text and update_conversation_segments.

2. Reprocess overwrites the transcript from a stale snapshot

reprocess_conversation (backend/routers/conversations.py) loads the conversation once (_get_valid_conversation_by_id), then process_conversation persists the conversation. An edit that lands after the snapshot load is clobbered when reprocess writes back.

Proposed fix: reprocess should not rewrite transcript_segments — it only regenerates derived data (structured summary, action items, vectors). Either re-read the latest transcript at write time, or narrow reprocess's persistence to derived fields only.

Already mitigated client-side (web, #9353)

The web UI now serializes segment saves (one PATCH in flight at a time) and makes editing and reprocess mutually exclusive, which removes the single-client race. It cannot fix the cross-client/cross-tab case or the reprocess root cause — those require the backend changes above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions