Skip to content

fix: don't reject same-day flights on UTC servers, and surface real validation errors. - #215

Open
scottjg wants to merge 3 commits into
punitarani:mainfrom
scottjg:fix/date-validation-tz-and-param-errors
Open

fix: don't reject same-day flights on UTC servers, and surface real validation errors.#215
scottjg wants to merge 3 commits into
punitarani:mainfrom
scottjg:fix/date-validation-tz-and-param-errors

Conversation

@scottjg

@scottjg scottjg commented Jul 13, 2026

Copy link
Copy Markdown

I was trying this MCP and it didn't really work out of the box for me:
Screenshot 2026-07-12 at 5 51 56 PM

so here's two fixes:

  1. Don't return a generic "Invalid parameter value". Neither an agent nor a person can tell what to fix from that. Pydantic already produces a specific message, so surface it: travel_date: Travel date cannot be in the past.

  2. Don't reject same-day flights for travelers west of UTC. The server validates that the travel date isn't in the past, but flight times are always local to the origin airport, and it compares against a naive datetime.now() (i.e. whatever timezone the server happens to run in. Mine runs on a platform with a UTC clock, so from ~5pm PST onward, today's date looks like yesterday and every same-day search is rejected. So, we fix validation to allow utc_today - 1. This should cover any possible timezone discrepancy. We also fix the validation to not allow putting a return date before the departure date on round trips.

Greptile Summary

This PR makes flight-date validation timezone-safe and improves validation feedback. The main changes are:

  • Allows dates that may still be today west of UTC.
  • Rejects round-trip and multi-city segments in reverse date order.
  • Returns detailed Pydantic errors from MCP operations.
  • Adds tests for UTC boundaries and segment ordering.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
fli/mcp/server.py Adds actionable formatting and explicit handling for Pydantic validation errors.
fli/models/google_flights/base.py Adds a UTC-based lower date bound that accounts for global timezone differences.
fli/models/google_flights/dates.py Applies the shared date bound and uses UTC today when adjusting old start dates.
fli/models/google_flights/flights.py Rejects reverse-ordered round-trip and multi-city segments.
tests/models/test_date_search_filters_validation.py Updates date-range validation tests to use the UTC boundary.
tests/models/test_flight_search_filters_validation.py Adds coverage for round-trip and multi-city segment ordering.
tests/models/test_flight_segment_validation.py Covers acceptance of the previous UTC day for same-day travel west of UTC.

Reviews (1): Last reviewed commit: "fix(models): stop rejecting same-day fli..." | Re-trigger Greptile

Context used:

  • Context used - CLAUDE.md (source)

scottjg and others added 2 commits July 12, 2026 17:34
…r value"

The tool handlers sniffed for the substring "validation error" in the
exception text and replaced it with a bare "Invalid parameter value",
discarding the message pydantic had already produced. Callers got no
indication of which parameter was wrong or why, so an agent had no way to
correct the call — the only route to the real cause was bisecting arguments
one at a time.

Catch ValidationError explicitly and flatten it into "field: message" pairs,
so a rejected search now reports "travel_date: Travel date cannot be in the
past". Applied to the date search handler too, which was not sniffing but was
dumping a raw pydantic repr as "Search failed".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The travel-date validators compared against datetime.now(), which is naive and
therefore resolves to whatever timezone the server happens to run in. In a UTC
container "today" is the UTC date, so for the last seven hours of every Pacific
day a same-day SFO evening departure was rejected as being in the past, even
though the flight had not taken off. It also meant validation behaved
differently on a developer's laptop than in production.

Travel dates are local to the origin airport, but Airport carries no timezone
data, so the validator cannot know the traveler's date. Real UTC offsets span
UTC-12 to UTC+14, so any local date is within one day of the UTC date;
anchoring to utc_today - 1 therefore never rejects a date that is still
today-or-future for the actual traveler. A genuinely past date now reaches
Google, which returns no flights — a better failure than refusing a valid
search.

DateSearchFilters.validate_to_date adopts the same floor, so a range ending
"today" is now accepted rather than requiring strictly-future; that rejection
was the same bug in miniature. The from_date clamp deliberately keeps plain
utc_today as its target, since anchoring it to the rejection floor would widen
MAX_PAST_FROM_DATE_DAYS by a day.

Relaxing the past-date check exposed a bug it had been masking: nothing
verified that a return date falls after the outbound date. A backwards round
trip was only ever rejected when the return date happened to land in the past,
so one entirely in the future was passed straight to Google. Add an ordering
check over the segments of round trips and multi-city itineraries.

The tests asserted against naive datetime.now() and so encoded the same
timezone dependence; they now anchor to UTC and pass identically under UTC,
America/Los_Angeles, Pacific/Kiritimati, Pacific/Midway and Asia/Kolkata.
test_round_trip_invalid_dates had been passing only because its return date was
in the past, not because the ordering was wrong — it now exercises the check it
was always meant to.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@alexechoi

Copy link
Copy Markdown

The utc_today − 1 grace window looks like the right call given there's no per-airport timezone data in the repo — it never rejects a date that's still today-or-future for the actual traveler, at the acceptable cost of letting a genuinely-past date through to Google. One edge case I think the loosened rule opens, though:

Inverted date ranges become constructible in DateSearchFilters. With to_date now allowed to be utc_today − 1, validate_and_adjust_from_date can clamp from_date past to_date: DateSearchFilters(from_date=utc_today−10, to_date=utc_today−1) constructs successfully with from_date > to_date and gets sent to Google as-is. The swap logic in validate_date_order is a field validator that has already run by then, so nothing re-checks the ordering. Clamping to min(current_date, to_date) (or re-asserting order at the end of the model validator) would close it — and notably, the reversed-dates assertion removed from test_date_search_today_to_date was covering exactly this path.

Two smaller notes: the "To date must be in the future" message no longer matches the new rule (it now accepts yesterday-UTC), and the multi-city branch reuses the "Return date … cannot be before departure date" wording when comparing leg 3 to leg 2, which will read oddly for someone debugging a 3-leg itinerary.

Coordination point: #226 (just opened) adds another parsed < datetime.now().date() validator in fli/models/google_flights/explore.py — the same naive-now pattern this PR removes. Happy to switch it to earliest_searchable_date() (and add the except ValidationError branch to the new explore executor) once this lands, in whatever shape it lands.

Review follow-ups on the utc_today - 1 grace window:

Allowing to_date at utc_today - 1 let validate_and_adjust_from_date clamp
from_date past to_date: from_date=utc_today-10, to_date=utc_today-1
constructed with from_date > to_date and was sent to Google as-is, since
the swap in validate_date_order is a field validator that has already run.
Cap the clamp at min(current_date, to_date), and restore test coverage for
the inverted-range path that the old reversed-dates assertion covered.

Reword "To date must be in the future" to "To date cannot be in the past"
to match the loosened rule, and give the multi-city ordering check its own
segment-numbered message instead of reusing the round-trip "Return date
... cannot be before departure date" wording for leg 3 vs leg 2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@scottjg

scottjg commented Aug 11, 2026

Copy link
Copy Markdown
Author

i updated the pr, as per your review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants