Skip to content

fix(hotel): ask for the missing check-out instead of a bad_dates failure

Hermes Agent requested to merge fix/hotel-search-missing-checkout-date into main

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_in but never extracted check_out (came back None).
  • 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 missing check_out defaults to "" upstream, which fails to parse, and it returns bad_dates.
  • The bad_dates log line only ever shows check_in because core.helpers.toolfmt's _kv formatter deliberately drops None/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

  1. ai/core/logics/assistant/graph/nodes/search.py — hotel_search_node now checks for a missing check_in/check_out (mirroring the existing city guard) and returns a clarify instead of searching, carrying city/check_in/pax_stated so the bridge widget can skip fields already known.

  2. telegram-bot/bot/search_setup.py — the hotel branch of start() previously only supported "both dates known" vs. "ask for check-in from scratch"; a stated check_in with check_out still missing fell through the "ask check-in" path and would have re-asked for a date the user already gave. It now pre-fills check_in and 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.

  3. ai/core/logics/hotel/hotel_search_logic.py — the bad_dates err() 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.py itself 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 existing hotel_out handling (which already reads data.get("check_in") for its confirmation message and the final _submit payload) to confirm the pre-filled check_in flows 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 / uv environment available here — the repo's pyproject.toml requires >=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.

Merge request reports

Loading