Skip to content

AutoOps Booking Failures: Urgent Alert + Full API Telemetry

Status: implemented, PR #11822 in review (2026-06-10). Follow-up to Always book an agreed-to appointment (PR #11753). Plan: .indusk/planning/autoops-unhandled-booking-errors/. Live-verified against AutoOps prod via the rejected-slot driver scenario (urgent email rendered, no job created).

Implementation adds a third deliverable beyond the original two: a lane-flip fallback — when AutoOps rejects the promised slot but the exact time is open in the other Waiter/DropOff lane, the job books there with a shop note stating the customer's real intent (caveated success; exception email fires). The urgent email fires only when no job lands at all.

Problem

AutoOps's book endpoint validates timeslots (confirmed 2026-06-10, falsifying the earlier "no booking restrictions" assumption). When it rejects a booking, the customer has already been told they're confirmed. Two production failures on 2026-06-10:

CallShopPromisedWhat happened
3577074aEAS Windsorsame-day 1:30 PM DropOffSlot real when offered in-call; gone (or inside a book-only lead-time rule, ~59 min) by post-call book. 400.
75568822Anteronext-day 9:00 AM WaiterAgent promised a slot its own availability result showed as empty (shop full until Jun 15). 400.

Both produced only the generic [AUTOOPS ERROR] booking email with a raw status code. Root-causing them took hours of log forensics because AutoOps requests/responses are not captured.

Current sequence (failure path)

Proposed sequence

The urgent email

Fires only when the book call fails and the extraction says the agent confirmed the booking. Reuses the booking-exception-email.ts rails and the getValidRecipients resolver.

  • Subject: [URGENT — Customer Told Booked, Booking FAILED]: <name> <date/time> (<team>)
  • Body: customer name + phone, vehicle, promised slot + Waiter/DropOff, service, diagnosed cause, nearest available alternative, call-details link.
  • Instructions (priority order):
    1. Book it manually in AutoOps — use the UI custom booking if the schedule refuses the slot.
    2. Only if it truly can't be honored: call the customer now and reschedule.
    3. Reminder: the customer currently believes they are confirmed for the promised time.

Diagnosis (the "clear reason" requirement)

On failure, one availability query for the promised service/lane/day, then classify:

ObservationDiagnosis sentence (example)
Promised slot absent, day has other slots"Promised 1:30 PM was not available at booking; same day had 2:30, 3:30."
Day empty entirely"No <lane> slots existed on Jun 11 for this service; first available Mon Jun 15 8:00 AM."
Slot present but book still rejected"AutoOps showed the slot available yet rejected the booking (suspected lead-time rule — slot was 59 min away)."
Diagnosis query itself failsRaw AutoOps error passes through; email still sends.

AutoOps API telemetry

Single chokepoint: the AutoOps client class (apps/web/lib/autoops/autoops.ts) — one request() wrapper plus the bespoke getAvailability fetch. Wrap once; every call emits one structured log via the existing Logger (inherits Datadog trace correlation):

autoops.api_call { method, endpoint, clientId, query|body, status, response, durationMs }

Then "show me every AutoOps exchange for call X" is one Datadog query (verified live 2026-06-11):

"AutoOps API call" (@call_id:<vapi-call-id> OR @external_call_id:<vapi-call-id>)

The ambient log context already stamps ids onto every entry: in-call dispatch entries carry the Vapi call id as @call_id (logger name: tools/dispatch); post-call workflow entries carry the internal call id as @call_id plus the Vapi id as @external_call_id (logger name: runAutoOpsBookingWorkflow), along with @team_id. The OR query returns the complete in-call + post-call set for one Vapi id. Each entry has @method, @endpoint, @request (full params/body), @status, @response, @durationMs.

Gotcha: Datadog free-text search only scans the message field — searching the raw call id as text returns nothing. Use the @attribute: facet syntax above.

PII note: responses carry customer names/phones/emails into Datadog. The same PII already flows through existing workflow logs; no new exposure class, standard retention applies.

Explicitly not solved here

Prevention. This closes the harm loop (minutes instead of never) but the booking still failed. The in-call fixes — only offer returned slots, near-term slot filtering, discrepancy playbook, service resolution, in-call commit validation — are tracked separately (see the AutoOps problems summary and .indusk/planning/autoops-service-resolution/).