The Word "Cancel" Was Doing Too Much Work

During a hands-on test of a hospital admissions system last week, someone cancelled a scheduled procedure for a test patient and discovered, almost by accident, that the same patient could now never be admitted to that hospital again. Not "cancelled and reschedulable." Not "cancelled, pending review." Permanently excluded, as if they had died. Nobody had designed it that way on purpose. Somewhere in the implementation, "cancel this one booking" had been wired to the same flag that should only ever fire for a genuine, final discharge reason, and the two had been quietly sharing a switch ever since.
It was caught because a person was sitting at a keyboard walking through the actual workflow, not because a design review flagged it or a test plan anticipated it. That's the part worth sitting with. The bug wasn't subtle in its consequence — a patient permanently unable to be readmitted is about as serious as a defect gets — but it was completely invisible to anyone reading the code for what it was supposed to do. The function was called something like "cancel," it lived in the cancellation workflow, and it did cancel the booking. It just also did something else, something with a much longer reach than its name implied, and nothing about reading it in isolation would tell you that.
This is a specific and common failure mode: a narrow action reuses a broad mechanism because the broad mechanism already exists and technically produces the right immediate result. Someone needed "cancelled" to mean "not currently active," found a flag that already meant "not currently active" for a different and much more serious reason, and pointed the new feature at it. It works, in the sense that the screen updates correctly and the test passes if the test only checks the thing it was written to check. What it doesn't do is stay contained. The flag doesn't know it's being asked to do two jobs. It just fires, and everything downstream that was built to trust "this flag means permanent exclusion" keeps trusting it, because from its point of view nothing has changed.
The reason this kind of thing survives so easily is that it's cheap to build and expensive to notice. Reaching for an existing state that already produces the behaviour you want is often the fastest way to ship a feature, and in plenty of systems it's harmless — reusing a "disabled" flag for two unrelated kinds of disablement rarely hurts anyone. The judgment that matters is knowing which flags are load-bearing for something serious enough that sharing them is dangerous, and treating those differently from the rest. A permanent-exclusion marker on a patient record is exactly that kind of flag. Anything that sets it needs to be traced back to a single, deliberate, well-understood trigger, not left open to being borrowed by whichever future feature happens to want similar-looking behaviour.
What makes this particular story land, rather than just being a cautionary tale about naming things carefully, is how it was found. Nobody discovered it by reading a specification or auditing the discharge-reason logic. It surfaced because someone was running a real workflow end to end — admit a patient, schedule them, move them through pre-procedure status, cancel — on a system that was about to go live, with a person actually watching what happened at each step rather than assuming the previous stage had been verified in isolation. Unit tests would have confirmed the cancellation flag got set. Integration tests, if they existed for this path, might have confirmed the booking disappeared from the schedule. Neither of those would have told you that the same patient could never be admitted again, because that consequence lives one layer away from the thing anyone was deliberately testing. It only shows up when you follow the actual person through the actual sequence of actions a real user would take, including the ones nobody thought to write down as a test case.
That's the generalisable point, and it applies well outside hospital systems. Automated tests are extremely good at confirming that the thing you thought to check still behaves the way you expected. They are much weaker at surfacing consequences nobody anticipated, because nobody writes an assertion for a side effect they don't know exists. A hands-on walkthrough of a real workflow, done by someone willing to notice when something feels wrong even when it technically "worked," catches an entirely different class of defect. Neither approach replaces the other. The coverage gap between "does this function do what it says" and "does using this feature do only what it says" is exactly where bugs like this one hide, and it's a gap that closes only when a human being follows the whole path rather than trusting that the pieces sum correctly.
There's also a quieter lesson about consequence and reversibility that's worth carrying into any system, not just clinical ones. Some actions genuinely should be permanent and hard to reverse — a deceased discharge, a closed financial year, a revoked credential. Most actions that look similar on a form should not be, and the correct default for anything new is reversible unless someone can name the specific reason it needs to be otherwise. When a team builds a new feature and it happens to land on a mechanism designed for the permanent case, that's the moment to ask directly whether this new use genuinely belongs there, rather than accepting that it technically produces the right screen. In this case, the fix was small once found: give cancellation its own state, leave the permanent-exclusion flag for the one situation it was built for, and let the two stop pretending to be the same thing. The hard part was never the fix. It was noticing, before go-live rather than after, that "cancel" and "never again" had been quietly sharing a name.


Share your thoughts