Skip to content

fix(tools): skip a malformed person doc instead of 500ing the conversation list#9494

Merged
kodjima33 merged 2 commits into
BasedHardware:mainfrom
ZachL111:zach/tools-skip-malformed-person
Jul 12, 2026
Merged

fix(tools): skip a malformed person doc instead of 500ing the conversation list#9494
kodjima33 merged 2 commits into
BasedHardware:mainfrom
ZachL111:zach/tools-skip-malformed-person

Conversation

@ZachL111

@ZachL111 ZachL111 commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

What

get_conversations_text (the service behind GET /v1/tools/conversations) builds its speaker-name lookup with an unguarded list comprehension:

people = [Person(**p) for p in people_data]

get_people_by_ids only guarantees the id field (it setdefaults id and its own comment notes legacy docs), while Person requires name. A single legacy or partially-written person doc that is missing name (or has a null field) raises ValidationError. That block is not wrapped in try/except, and the route handler at routers/tools.py does not catch it either, so one bad person doc turns the entire conversation-list response into an HTTP 500.

Fix

Skip and log the malformed person, then continue, so that speaker's name just goes unresolved instead of failing the whole list. This mirrors two guards that already sit right next to it:

  • the deserialize_conversation loop in the same function (per-item try/except + continue)
  • the sibling search_conversations_text, whose identical Person(**p) line is already inside a try/except

Confined to utils/retrieval/tool_services/conversations.py; no route, model, or contract change.

Testing

  • python -m pytest tests/unit/test_tools_router.py -> 86 passed, 1 skipped. The new TestGetConversationsTextMalformedPerson feeds one valid and one malformed person doc and asserts the call returns the conversations (good speaker resolved, malformed one skipped) instead of raising.
  • black, pyright (0 errors on the changed source), and check_module_stub_pollution (0 violations) all clean.

Review in cubic

…ation list

get_conversations_text built its speaker-name people list with an unguarded
[Person(**p) for p in people_data]. get_people_by_ids only guarantees the id
field (it setdefaults id for legacy docs) while Person requires name, so one
legacy or partially-written person doc missing name raises ValidationError. That
block is not wrapped in try/except and the GET /v1/tools/conversations handler
does not catch it, so a single bad person doc 500s the whole conversation list.

Skip and log the malformed person and continue (that speaker's name just goes
unresolved), matching the deserialize_conversation loop in the same function and
the guard in the sibling search_conversations_text. Internal to utils/, no
route/model/contract change.

Verification: python -m pytest tests/unit/test_tools_router.py -> 86 passed, 1
skipped. black + pyright clean; check_module_stub_pollution -> 0.
@ZachL111

Copy link
Copy Markdown
Contributor Author

Heads-up on CI: the single red check (Backend unit suite) is the pre-existing main breakage, not this PR. The failing test is tests/unit/test_app_client_schema_inventory.py::test_inventory_strict_raw_decode_site_gate_fails_with_actionable_sites, which hardcodes an app/lib/backend/schema/app.dart raw-decode line number that drifted from :31 to :32 when #9470 landed on main. The one-line fix is up as #9482; once that merges this suite goes green on rerun.

This PR only touches utils/retrieval/tool_services/conversations.py and its test, and its own tests pass: tests/unit/test_tools_router.py -> 86 passed, 1 skipped.

@Git-on-my-level Git-on-my-level added candidate-clean-pr Small, low-risk, well-scoped PR candidate (pending sequencing/maintainer sign-off) ci-failing-pre-existing CI check failing for reasons pre-existing/unrelated to this PR (red main) labels Jul 11, 2026
@Git-on-my-level

Copy link
Copy Markdown
Collaborator

Thanks @ZachL111 — this is a clean, well-scoped fix. A couple of notes:

Verified correct. The source change wraps the Person(**p) construction in a per-item try/except + continue, matching the existing deserialize_conversation guard in the same function and the function-level guard in search_conversations_text. people is initialized to [] earlier in get_conversations_text, so people.append(...) inside the loop is safe. No route, model, or contract change — just resilience. The failure mode described (a legacy/partial person doc missing nameValidationError → HTTP 500 on GET /v1/tools/conversations) is real and worth fixing.

Tests. The new TestGetConversationsTextMalformedPerson test is solid — it feeds one valid + one malformed person doc, asserts the call completes without raising, and checks the good speaker still resolves. It passed in CI alongside the rest of test_tools_router.py (86 passed, 1 skipped).

CI failure is unrelated. The only failing check is test_app_client_schema_inventory.py, which runs a Dart-side schema inventory scan over app/lib/backend/. This PR touches only Python backend code, so that failure is pre-existing and not introduced here.

I have not formally approved because CI is red on this branch (approval gate requires passing checks), but the change itself looks correct and low-risk. Once the unrelated CI failure is resolved (or this is confirmed pre-existing), a maintainer should be good to approve/merge.

Labeling candidate-clean-pr + ci-failing-pre-existing for visibility.

@ZachL111

Copy link
Copy Markdown
Contributor Author

Thanks David, appreciate the thorough read. Agreed on all points. Confirmed the only red check is the pre-existing tests/unit/test_app_client_schema_inventory.py gate (the app.dart raw-decode line drift from #9470), not this PR; the one-line fix is up as #9482 and this suite goes green on rerun once it merges. No code changes here otherwise.

@kodjima33 kodjima33 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backend hardening fix — reviewed, approving.

@kodjima33 kodjima33 merged commit dd70db5 into BasedHardware:main Jul 12, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

candidate-clean-pr Small, low-risk, well-scoped PR candidate (pending sequencing/maintainer sign-off) ci-failing-pre-existing CI check failing for reasons pre-existing/unrelated to this PR (red main)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants