fix(hotel): ask for the missing check-out instead of a bad_dates failure
Incident
A Telegram user searched an Istanbul hotel with check_in=2026-08-31 (a perfectly valid date, ~2 days out). The search failed:
core.logics.assistant.graph.resilience | hotel_search_node: search returned control: ERROR code=bad_dates check_in=2026-08-31
The log made it look like the valid check_in was rejected. It wasn't.
Root cause
- The router's slot extraction filled
city+check_inbut never extractedcheck_out(came backNone). -
hotel_search_node(ai/core/logics/assistant/graph/nodes/search.py) only guards against a missing city — there was no equivalent check for missing check-in/check-out — so it searched anyway. -
HotelSearchLogic.search_state(ai/core/logics/hotel/hotel_search_logic.py) then tried to parse both dates; the missingcheck_outdefaults to""upstream, which fails to parse, and it returnsbad_dates. - The
bad_dateslog line only ever showscheck_inbecausecore.helpers.toolfmt's_kvformatter deliberately dropsNone/empty fields everywhere (a real, correct design choice for the LLM-facing compact tool-output format — NOT a bug in general) — but at this one call site it silently erased the one field that actually mattered for debugging.
Fix — three changes, one commit
-
ai/core/logics/assistant/graph/nodes/search.py—hotel_search_nodenow checks for a missingcheck_in/check_out(mirroring the existing city guard) and returns aclarifyinstead of searching, carryingcity/check_in/pax_statedso the bridge widget can skip fields already known. -
telegram-bot/bot/search_setup.py— the hotel branch ofstart()previously only supported "both dates known" vs. "ask for check-in from scratch"; a statedcheck_inwithcheck_outstill missing fell through the "ask check-in" path and would have re-asked for a date the user already gave. It now pre-fillscheck_inand jumps straight to the check-out calendar in that case — the partial-skip behavior the module's own docstring already promised ("both skipped if the nights were already given") but the code didn't actually implement. -
ai/core/logics/hotel/hotel_search_logic.py— thebad_dateserr()call now marks a genuinely missing date as"<missing>"instead of leaving it empty, so it survives_kv's empty-field filtering. Scoped to this one call site —toolfmt.pyitself is intentionally unchanged, since its drop-empty-fields behavior is correct everywhere else it's used (RECO/ASK/BOOKING/etc. — changing it globally would bloat every LLM tool-output line for no benefit).
Test
ai/evals/tests/test_langgraph_flow.py::test_clarify_hotel_dates_when_check_out_missing — offline, no LLM/network/DB, same Harness pattern as the existing test_clarify_flight_route_when_no_route. Drives a hotel_search decision with city + check_in but no check_out and asserts the turn returns a clarify(ask=hotel_dates) carrying the known city/check_in instead of a bad_dates error.
Verification
- Traced the fix through
hotel_search_node->search_setup.start->on_calendar's existinghotel_outhandling (which already readsdata.get("check_in")for its confirmation message and the final_submitpayload) to confirm the pre-filledcheck_inflows through correctly with no other change needed. - Syntax-checked (
py_compile) all four changed files. -
Could not execute the actual test suite in this session (no Python 3.14 /
uvenvironment available here — the repo'spyproject.tomlrequires>=3.14, this sandbox only has system Python 3.11). Please let CI run the suite, including the new test, before merging.
Not fixed here (separate, pre-existing, out of scope)
hotel_search_node's existing missing-city branch returns ask="hotel_dates" with no city/need_dates/pax_stated payload — so search_setup's widget defaults to asking for dates when city is what's actually missing (and the widget has no city-collection step at all today, so a missing city currently has no working UI path). Found while investigating this incident; distinct bug, worth its own fix, not touched here to keep this MR scoped to the reported incident.
Also separately flagged in MR !31 (merged) (still open): the Shamsi/Hijri→Gregorian hotel-date conversion fix (7ff3991) is on beta but not an ancestor of main — unrelated to this incident but in the same area.
Not done (by design)
- Not merged — opened for review only.
- No production deploy, service restart, or live database/system touched.