Appearance
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 therejected-slotdriver 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:
| Call | Shop | Promised | What happened |
|---|---|---|---|
3577074a | EAS Windsor | same-day 1:30 PM DropOff | Slot real when offered in-call; gone (or inside a book-only lead-time rule, ~59 min) by post-call book. 400. |
75568822 | Antero | next-day 9:00 AM Waiter | Agent 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):
- Book it manually in AutoOps — use the UI custom booking if the schedule refuses the slot.
- Only if it truly can't be honored: call the customer now and reschedule.
- 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:
| Observation | Diagnosis 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 fails | Raw 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/).