Building & Shipping

I Blamed the Integration. It Was Never Connected.

Brett Ridenour Brett Ridenour · Published August 2026

An operator on Freebo went eight days without a booking. Not a full outage — checkout still worked, quotes still returned, the widget still loaded. Just a slow, quiet drought where the number that should have been going up wasn’t.

I had a theory the second I saw it. A week earlier I’d shipped a Google Calendar integration and helped the operator tighten his availability rules the same afternoon. Two changes, one suspicious result. The Google Calendar sync was the new thing, so obviously the Google Calendar sync was the bug. I opened a session at 5am to prove it.

I was wrong about the cause. I was wrong about the fix. And I was wrong in a way that’s worth writing down, because it’s the exact shape of a mistake I’ve made three or four times before and will absolutely make again.

The obvious explanation was the wrong explanation

The Google Calendar feature is supposed to let an operator sync their personal calendar into Freebo so that a lunch meeting or a maintenance day gets treated as a hard block on the booking widget. It writes to a calendar_connections row when the operator completes OAuth, then a background job pulls events every few minutes and inserts them as availability blocks.

My hypothesis was clean: the sync was importing something it shouldn’t — an all-day birthday event, a recurring reminder — and quietly zeroing out slots. I could picture the exact bug. I’ve written that bug before.

Then I ran the query.

The operator never finished the OAuth flow. He’d started it, hit a Google consent screen he wasn’t sure about, and closed the tab. There was no sync. There had never been a sync. The feature I was about to spend three hours debugging had never once run against his account.

The thing I blamed had, at the data layer, done nothing. Not “did the wrong thing.” Literally nothing.

The real culprit was in a rule I edited myself

Once the Google Calendar theory died I had to actually look. I pulled the availability-rules audit log and sorted by updated_at. Two rows lit up at the top:

Blackout rules widened from Sat/Sun to all seven days

Both had been edited at 16:45 UTC on August 3. Both had the same change: the days_of_week field went from ["sat", "sun"] to ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]. Two weeks earlier, at the operator’s request, I’d widened his weekend-only blackouts to cover the whole week. It was a two-minute change. I hit save and moved on.

Here’s what those two rules were quietly doing to his catalog:

  • 2-hour charters starting between 08:00 and 10:30 ET: blocked. Every day.
  • 3-hour charters starting between 07:00 and 09:30 ET: blocked. Every day.

Every longer charter was untouched — the rules keyed off duration alone. But the entry-level 2h product, which is also the top converter, now offered a 07:00 and 07:30 slot and then nothing until 11:00. A customer landing at 9am and looking for a morning trip would see the top of the calendar and bounce.

The decisive test was a live quote against production:

Live quote endpoint proving blackout is biting

The rules were working exactly as I’d written them. That was the whole problem. They were also blocking the exact hours his customers wanted to book.

The conversion data made it undeniable

To sanity-check the theory, I pulled product→quote conversion by week. This is the rate at which someone who lands on a product page requests a specific-time quote — the moment they actually try to book.

  1. Week of Jul 12
    51.5%
    97 product views, 50 quote attempts. Baseline.
  2. Week of Jul 19
    62.2%
    98 product views, 61 quote attempts. Peak of the range.
  3. Week of Jul 26
    54.8%
    199 product views, 109 quote attempts. Wider sample, same range.
  4. Week of Aug 2
    30.0%
    110 product views, 33 quote attempts. Blackout rule widened Aug 3. Step down.
  5. Week of Aug 9
    33.3%
    54 product views, 18 quote attempts. Holds at the new lower rate.
  6. Week of Aug 16
    33.3%
    9 product views, 3 quote attempts. Still holding.

Three stable weeks in the 51–62% band. Then a hard step down to about a third of visitors requesting a quote, and it never recovered. The break lined up with the rule edit, not the calendar deploy.

I tried to kill the finding four different ways and couldn’t. It wasn’t a traffic-mix artifact — the same acquisition source dropped from 55% to 29% on its own. It wasn’t bots — zero bot-classified events on either side of the break. It wasn’t a code deploy — the change that shipped that day was an unrelated post-booking staff notification. It wasn’t a downstream bug that shipped later, because that one had already been fixed and conversion stayed flat.

The only thing that changed on August 3 was the rule I widened.

Before opening the debugger, open the audit log.

— what I actually should have done

The lesson I keep having to relearn

Two things ship close together. One is new and exciting. One is a two-minute config change. Something breaks. I always suspect the new and exciting thing. It’s almost never the new and exciting thing.

The new feature has been code-reviewed, tested, staged, and I have all its edges in my head because I just built it. The two-minute config change had no test, no review, no artifact I remember writing. When something regresses, that config change is the thing I don’t see, because I already forgot I made it.

The check that would have saved me two hours was one query: “did the thing I’m blaming actually run?” If the answer is no, the whole hypothesis is dead before it starts. In this case the answer was a single empty table. Instead I started with SELECT * FROM sentry_events and worked my way inward, looking for a bug I’d already talked myself into.

The fix was five seconds — narrow the two blackout rules back to weekends. But the real fix is the reflex. Next time I ship something scary and something else breaks the same week, the first tab I open is the audit log, not the debugger. The debugger tells you what the code did. The audit log tells you what you did. Nine times out of ten, that’s where the answer is.