Fix /v1/users/people and /v1/conversations 500 errors#5441
Conversation
Legacy Firestore person documents may lack these fields, causing ResponseValidationError (500) on /v1/users/people for 8 users. Make both fields Optional with None default. Fixes part of #5423 Co-Authored-By: Claude Opus 4.6 <[email protected]>
get_people(), get_person(), get_person_by_name(), get_people_by_ids() all returned raw to_dict() without the document ID. Legacy docs missing the 'id' field caused ResponseValidationError on Person model. Fixes #5423 Co-Authored-By: Claude Opus 4.6 <[email protected]>
Enables the list endpoint to use this lighter function that skips loading full base64 photo content per conversation. Fixes part of #5424 Co-Authored-By: Claude Opus 4.6 <[email protected]>
GET /v1/conversations was loading full base64 photos for every conversation via @with_photos decorator. 50 convos x 1.2MB = 57MB exceeded Cloud Run 32MB response limit. The list endpoint doesn't need photo content — individual conversation GET still loads them. Fixes #5424 Co-Authored-By: Claude Opus 4.6 <[email protected]>
936f25a to
9f31394
Compare
Greptile SummaryThis PR fixes two production 500 errors:
Confidence Score: 3/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant Client
participant ConvRouter as GET /v1/conversations
participant ConvDB as conversations.py
participant Firestore
Client->>ConvRouter: GET /v1/conversations?limit=50
ConvRouter->>ConvDB: get_conversations_without_photos(uid, limit, ...)
ConvDB->>Firestore: Query conversations collection (no photos subcollection)
Firestore-->>ConvDB: conversation docs (no base64 photos)
ConvDB-->>ConvRouter: List[dict] (~KB not ~57MB)
ConvRouter-->>Client: 200 List[Conversation] (photos=[])
Note over Client,Firestore: Individual GET still loads photos
Client->>ConvRouter: GET /v1/conversations/{id}
ConvRouter->>ConvDB: get_conversation() with @with_photos
ConvDB->>Firestore: Fetch doc + photos subcollection
Firestore-->>ConvDB: conversation + base64 photos
ConvDB-->>ConvRouter: dict with photos populated
ConvRouter-->>Client: 200 Conversation (photos=[...])
|
backend/database/users.py
Outdated
| @@ -88,8 +98,10 @@ def get_people_by_ids(uid: str, person_ids: list[str]): | |||
| for i in range(0, len(person_ids), 30): | |||
| chunk_ids = person_ids[i : i + 30] | |||
| people_query = people_ref.where("id", 'in', chunk_ids) | |||
There was a problem hiding this comment.
Legacy documents silently excluded by get_people_by_ids
This query filters on the id field stored inside the document data. For legacy person documents that were created without an explicit id field in their data (only the Firestore document key), this query will return no results — setdefault('id', person.id) only helps after a document is already fetched, not during the query itself.
While create_person always writes id into the data, any legacy documents that predate this pattern will be silently dropped from get_people_by_ids results. This is a pre-existing issue, but since the PR is already fixing person-related 500 errors, it's worth noting that callers relying on get_people_by_ids (e.g. the shared conversation endpoint that fetches speaker names) may still return incomplete data for legacy users. Consider migrating legacy docs or querying by document key using Firestore's document().get() pattern instead.
Reviewer feedback: where("id", "in", ...) misses legacy docs that
don't have a stored 'id' field. Use db.get_all() with doc refs instead.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
10 tests covering: - Person model resilience with missing created_at/updated_at (#5423) - Doc ID injection in get_people, get_person, get_people_by_ids (#5423) - Conversations list endpoint uses without-photos function (#5424) - get_conversations_without_photos supports folder_id/starred (#5424) Co-Authored-By: Claude Opus 4.6 <[email protected]>
Co-Authored-By: Claude Opus 4.6 <[email protected]>
- Large batch test for get_people_by_ids (>30 IDs, old where-in limit) - Empty list boundary test - Verify get_conversations_without_photos lacks @with_photos decorator - Verify get_conversations retains @with_photos for individual use Co-Authored-By: Claude Opus 4.6 <[email protected]>
Local dev live test evidence (dev Firestore)Test 1: Legacy person doc (no
Test 2: Real user people data
Test 3:
Test 4: Unit tests (14/14 pass) by AI for @beastoin |
All Checkpoints Passed — Ready for Merge
Live validation: Tested against dev Firestore ( Test evidence: All 14 unit tests pass ( PR is ready for merge. Awaiting explicit merge approval. by AI for @beastoin |
Review Feedback from Combined Verification (PR #5445)Hey @yuki — 14/14 tests pass, live API verified, legacy docs handled correctly. Quick note from architecture review: NOTE:
|
Per kelvin UAT review: callers must not assume result order matches input person_ids order. Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
Post-deploy verification — PR #5441 (via combined #5459) Deploy completed 01:17 UTC. Verified at T+14m (01:31 UTC): GET /v1/users/people — all 200 OK, zero 500s
GET /v1/conversations — all 200 OK, zero 500s
Overall backend health — zero errors (severity >= ERROR) since deploy Post-deploy monitoring scheduled: T+30m, T+1h, T+2h, T+4h, then every 4h for 24h. by AI for @beastoin |
Physical Device E2E — PR #5441Pixel 7a physical device test on combined branch
Combined with 14/14 unit tests and live API verification — PASS. |
Summary
/v1/users/peoplereturns 500 (ResponseValidationError) becauseget_people()returns rawto_dict()without injecting the Firestore document ID, andPersonmodel requiresid,created_at,updated_atas non-optional. Fix: inject doc ID viasetdefault('id', doc.id)in all person query functions + makecreated_at/updated_atOptional./v1/conversationsreturns 500 ("Response size was too large") because@with_photosdecorator loads full base64 photo content for every conversation in the list. 50 convos × ~1.2MB = ~57MB exceeds Cloud Run 32MB limit. Fix: switch list endpoint toget_conversations_without_photos()(photos load on individual GET only).Changes
backend/models/other.pyPerson.created_atandupdated_atOptional with None defaultbackend/database/users.pyget_person(),get_people(),get_person_by_name(),get_people_by_ids()backend/database/conversations.pyfolder_idandstarredparams toget_conversations_without_photos()backend/routers/conversations.pyget_conversations()toget_conversations_without_photos()backend/tests/unit/test_people_conversations_500s.pybackend/test.shReview cycle changes
get_people_by_ids()fromwhere("id", "in", ...)todb.get_all()with document refs — legacy docs missing storedidfield would not be returned by the query approach.@with_photospresence/absence.Test evidence
get_conversations_without_photosreturns 0 photosCloses #5423
Closes #5424
🤖 Generated with Claude Code
by AI for @beastoin