Fix bot purge failure on chunked JSON deserialization - #927
Conversation
ReferenceResolvingConverter called Utf8JsonReader.Skip(), which throws "Cannot skip tokens on partial JSON" when accounts stream in via SequentialAccess. Empty collections (array_agg -> NULL) hit this on every bot account and aborted the whole purge. - Replace all Skip() with streaming-safe JsonElement discard; null means empty collection - Emit '[]' instead of null for empty collections in JsonQueryBuilder - Skip (and log) single failing accounts in DeleteAllBotsAsync
ReviewNice catch on the root cause — the diagnosis is exactly right, and the stack trace even proves the sharpest part of it: the throw happened on a 1.
|
ReferenceResolvingConverter called Utf8JsonReader.Skip(), which throws Cannot skip tokens on partial JSON when accounts stream in via SequentialAccess with a non-final buffer. Any null or unknown value (e.g. an empty array_agg collection) aborted the whole bot purge. - Replace all Skip() calls with TrySkip(), preserving reader position semantics without allocating throwaway JsonDocuments - Make DeleteAllBotsAsync resilient per account: skip and log single failures, detach the failed graph from the change tracker, count deletions only after a successful save, never swallow cancellation, and rethrow when any account failed so the purge is retried instead of switching the feature off with bots still in the database - Split the purge into collect/try-delete helpers with an explicit delete-outcome enum - Add a regression test driving the converter on a partial buffer and a chunked FromJson scenario (cherry picked from commit d05bf9a)
|
Follow-up on the cascade question from my review above: I wrote it up as #933 and implemented the trigger route in #934. Short version of what I got wrong in that first comment: the Relevant to this PR: #934 touches Generated by Claude Code |
#927 moved the deletion of a single bot account into TryDeleteBotAccountAsync. Kept that structure and removed the manual deletion of the item storages there instead: the delete triggers remove them now, and EF's own delete of the storage would find the row already gone and fail the save. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q9QAbEmp9rhnSWqZvkVaeN
JsonObjectLoader loads accounts via Postgres JSON + SequentialAccess.GetStream() (forward-only, chunked), and JsonObjectDeserializer passes that stream straight to JsonSerializer.Deserialize, so Utf8JsonReader runs with isFinalBlock: false. ReferenceResolvingConverter called Utf8JsonReader.Skip() in 5 places — which throws Cannot skip tokens on partial JSON on non-final buffers. The hot path was ReadProperty's else branch: an adder-only collection property (e.g. Account.RawAttributes, empty for bots) whose SQL array_agg(...) yields JSON null instead of []. One such account aborted the entire purge loop.
What changed