fix: return a failure reason from POST /ride - #30
Merged
Conversation
A rejected ride start answered with a bare `{ success: false }`, so every
cause looked identical to the app - it just reverted to the route details
screen with no way to tell a missing route from a ride outside the startable
window. Return a machine-readable `reason` the client can tag its error
report with, and log the in-time rejections (which previously logged nothing
at this level) at warn.
planecore
force-pushed
the
fix/tag-ride-start-failures
branch
from
September 3, 2026 07:08
38ccfa1 to
060e2b5
Compare
planecore
approved these changes
Sep 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
A user reported a live ride that wouldn't start on Android. The tap worked — the sheet appeared and then reverted a second later — but nothing surfaced anywhere: no error on screen, nothing in Sentry, and no way to tell from the server side which rejection it was.
startRideNotificationsanswers{ success: false }for every failure, soroute_not_found,ride_in_pastandride_in_futureare indistinguishable to the client. On top of that,RideNotInTimeErrorwas deliberately skipped by the logger, so the two most likely causes left no trace at all in this handler.What changed
RideNotInTimeError/NotFoundRouteForRidenow carry areason, andrideFailureReason()maps anything else tointernal_error.POST /ridereturns{ success: false, reason }.reasonis one ofroute_not_found|ride_in_past|ride_in_future|internal_error.warn(they aren't server faults, but they need to be greppable); everything else keeps logging aterror, now with the reason attached.Status codes are unchanged, so shipped clients are unaffected — they only check
success, and axios rejects a 500 either way.Follow-up
The app side reads this
reasonand attaches it to a Sentry tag (ride_start_reason) alongside the failure stage and HTTP status — that PR is separate.Not included here, worth deciding on:
ride_in_futurestill answers 500, which inflates any server error-rate metric. A 409/422 would be more honest and wouldn't break clients.POST /rideis rate limited to 10 requests / 10 min percf-connecting-ip(src/routes/api.ts). On Israeli cellular CGNAT many subscribers share one public IP, so a user on mobile data can be rejected by other people's traffic. Once the client tags are live we'll see whether 429 shows up in practice.Testing
bun test— 101 pass, 0 fail.tsc --noEmitclean.