At 5:55 PM a staff checkout for a live event refused to save. The cart still carried a recurring monthly plan, and the screen said auto-renew off.
That looked like a contradiction. It wasn’t. The checkout had a staff SKIP-payment path, with a guard meant to stop anyone from skipping payment on a cart that would keep billing every month. The plan in the cart was recurring, so the guard blocked the save. The checkout summary was showing the auto-renew state of the previous plan. It hadn’t caught up after the plan changed.
Reaching for the wrong component
We debugged it live in the browser, starting with the part that looked impossible. The summary said auto-renew was off, and the skip guard behaved as if it were on. The guard was the obvious suspect because it was the thing producing the block. With a live event sitting unsaved, we were already half-reaching for its condition to see what we’d have to loosen.
That instinct was wrong. Loosening the guard would have opened the exact hole it exists to close: a recurring plan skipping payment because the screen said it was safe.
The guard was never reading the summary. It evaluated the cart state. Its rule was effectively this:
if cart contains a recurring monthly plan: block SKIP-paymentThe summary was a separate rendering path, and it had kept the old plan’s value for the auto-renew indicator. Once we split the two paths, the mystery went away. The guard saw the current cart. The customer-facing summary showed a stale projection of the previous one.
A visible field is evidence, but it isn’t automatically the source of truth for the action that failed. Here the failure was the useful clue. The guard was doing exactly what it should against the data it actually received.
Three ways to get the event saved
The first was to weaken or bypass the SKIP-payment guard. We rejected that. A cart with a recurring monthly plan is exactly the cart the guard exists to stop. Making the guard obey a stale summary would turn a UI cache miss into an authorization hole.
The second was to stop and repair the stale UI path before saving the event. That was the product fix, but it was the wrong incident response. The defect was in the UI cache or state refresh, and fixing it meant changing a broader path while an event record sat blocked in production.
The third was to make the submitted field match the state the guard should see for this one save, then use the existing controlled workflow.
auto_renew = 0skip-and-saveIt was a one-line client-side change that set the hidden auto_renew field to 0. Then the staff member completed the existing skip-and-save action. No server rule changed and no guard was disabled. The cart submitted a non-recurring state, which is the only state under which skipping payment is valid.
This was a targeted operational bypass, and it left the underlying defect in place. The immediate job was to save an event without granting an invalid payment skip. The follow-up job was to fix the stale summary so the UI shows the same plan state the guard evaluates.
Two facts, two tickets
The incident left behind two facts that should not be merged into one ticket.
The payment guard works. It detects a recurring monthly plan and blocks staff skip-payment, and that behavior has to stay intact.
The summary can show auto-renew off after the plan has changed, because it is still holding the previous plan’s state. That is the defect. The follow-up ticket covers the UI caching and state-refresh path. Its reproduction changes plans, then compares the summary’s auto-renew display against the hidden submitted auto_renew value.
We also wrote down the rule that got us out: when a guard and a screen disagree, trace each one back to its own input before changing either. Here the screen was describing an old plan while the guard protected the current cart. The one-line change got the event saved, and the ticket means the next operator won’t have to find the same mismatch under pressure.