Asking again should not cost you the answer
Press the button a second time and the first answer disappears, even when the second attempt fails. A small ordering bug in our allergen tool, and the general rule it taught us: nothing replaces a thing until the replacement exists.
Filling in allergen information for a full menu is one of the least popular jobs in a kitchen, so we built a tool that reads a dish's name and description and proposes what it probably contains. A person then confirms it, line by line, before any of it reaches a guest. The gap between the proposal and the confirmation is the entire point of the feature: a machine guess about nuts is a legal problem, a chef's confirmation is not.
When a proposal is waiting and you press "ask again", the old one should be set aside so there are never two competing answers on the screen. Perfectly reasonable.
It was done in the wrong order. The old proposal was set aside first, and then the new one was requested.
What that costs on a bad afternoon
Most of the time nothing, because the second request succeeds and replaces the first. But if the request fails — the service is briefly unreachable, the connection drops, anything ordinary — you now have nothing. The old proposal has already been retired, and nothing in the system brings one back.
The error message, which was accurate about the request, read: "No suggestion could be made just now. Allergens can still be filled in by hand." Entirely true, and said having just thrown away the answer that would have saved somebody the typing.
The general rule
Nothing supersedes a thing until the thing that supersedes it exists.
It sounds obvious written down, and it is broken constantly, because the tidying-up code is short and the fetching code is long, so the short one drifts to the top. You will find the same shape in plenty of places outside software. A kitchen that scraps the old prep list before the new one is printed. A rota that comes down on Thursday for a replacement that goes up on Friday. A stock count that clears yesterday's figures at the start rather than at the end.
In each case the ordering is invisible until something interrupts the middle. Then the interruption costs you not just the new thing, but the old one too.
The fix, and its control
The replacement now happens after the new proposal arrives. A failed request leaves your existing answer exactly where it was, still on screen, still confirmable.
The test that guards this has a second half which matters more than the first. It checks that a successful request still leaves exactly one proposal waiting. Without that, moving the tidy-up later could just as easily have produced two competing answers on the screen — which is the problem the original ordering existed to prevent, and would have been a worse bug than the one being fixed.
Any change that moves when something happens needs both halves tested: the case that was broken, and the case that was working. Otherwise you have traded one failure for another and only measured one of them.