Permanent by default
A temporary PHP fix reached 20M installs because a label is not a control. The mechanism, the pattern, and what must now be true in production.
A temporary PHP fix I wrote in 2014 is now running in nearly 20 million installations. Today I am deprecating it. The word temporary held for none of those installs. It described my intent at the moment I committed the code. It did not describe anything the runtime enforced, because the runtime does not read intent. It reads instructions, and the instructions worked.
That is the position. A label is not a control. Calling a fix temporary attaches a plan to a piece of code, and a plan is not enforced by the system executing that code. Nothing in the artifact expired. No flag flipped after a set date. No dependency refused to load. The code did exactly what it was written to do, on schedule, for every consumer who pulled it. The gap between what I named it and what it became was never the code’s problem. It was mine, and it stayed mine until the install count made it everyone’s.
If a fix runs in production and nothing forces its replacement, it is permanent by default. Permanence is the resting state of working code. The install count is the evidence. Twenty million systems did not adopt a stopgap. They adopted a component that functioned, and functioning is the only property that scales without supervision. The temporary designation existed in exactly one place that mattered to no runtime: my memory of what I meant to do next.
The assumption in 2014 was simple. This holds until the real fix ships. Temporary implied a second action would follow the first, and that the second action would retire the first. Nothing scheduled that second action. Nothing owned it. Nothing gated the release on its completion. The dependency on future work was real, but it was tracked by intent alone, and intent has no enforcement point. Whether a maintained replacement was ever built is not confirmed.
The fix carried no expiry at write time. No deprecation date. No hard stop that would surface the debt when the date passed. A temporary control that cannot signal its own end is indistinguishable, from the outside, from a permanent one. Consumers pulling the code had no field to read that said this is provisional. They read behavior, and the behavior was stable. The provisional status lived in the commit history and in my head. Neither of those is loaded at runtime.
The assumption also transferred the moment the code was published, and it did not transfer intact. I assumed a follow-up. Every installer assumed a working dependency. Those are not the same assumption, and the second one is the one that compounded. Each install added a system that treated the fix as settled infrastructure. None of them inherited the caveat, because the caveat was never encoded in the thing they installed. They received the code. The label stayed behind.
Nearly 20 million installs is the change. The code did not change. Its role did. It moved from patch to dependency, from a line I meant to delete to an interface other systems now expect to be present. Scale performs that conversion without asking. Adoption does not distinguish between a permanent design decision and a shortcut left running. It only records what works and reuses it.
Time compounded the same effect. The gap between 2014 and today is twelve years. Every year in that gap increased the number of systems assuming the fix’s behavior and raised the cost of removing it. Each install narrowed the exit. A stopgap that survives long enough stops being a stopgap and becomes an assumption others build on, and assumptions others build on cannot be quietly withdrawn. The removal cost is now distributed across every consumer who never agreed to hold provisional code.
Today it is being deprecated. Deprecation is the first enforcement action applied to this fix in twelve years. It is the expiry that should have existed at write time, arriving twelve years and twenty million installs late. Whether a maintained replacement exists for every consumer is not confirmed. What is confirmed is that the only mechanism now forcing the question is a manual deprecation notice, issued by the same person who called the code temporary and let the runtime decide otherwise.
The failure has one mechanical location. It is the enforcement point that was never written. A permanent component and a temporary one share an identical lifecycle unless something removes the temporary one when a condition is met. That removal is a control. It has to be code the system runs: a hard expiry, a dependency that stops resolving, a build that fails past a date. None of that was present. With no such control, the fix followed the only lifecycle available to it. It installed, it resolved, it executed, it persisted. That is the lifecycle of infrastructure, not of a stopgap.
The observable behaviour across nearly 20 million installs was uniform. No install surfaced a provisional warning. No resolution failed on a date. No version refused to load. Every consumer received code that functioned and continued to function. The absence of those signals is the mechanism, not a side effect of it. A provisional control that cannot signal its own end is, from every external position, a permanent one. Consumers read behaviour. The behaviour was stable, so the classification they applied was stable.
The dependency on a follow-up existed only as tracked intent. Intent has no runtime representation. There was no field to read and no flag to check, so nothing ever checked. Whether a maintained replacement was built is not confirmed. What is confirmed is that the fix’s continued operation was never conditional on that replacement existing. The two were never linked by anything the system executes. The link lived in commit history and in memory, and neither is loaded when code runs.
The pattern generalises directly from that mechanism. Any code whose retirement depends on future work that nothing enforces is permanent from the moment it ships. The provisional status is a property of the author’s plan. It is not a property of the artifact, and only the artifact is executed. This holds regardless of language, platform, or install count. The PHP fix is one instance. The structure is the constant.
The same structure appears wherever a label carries a lifecycle that no control enforces. A firewall exception opened with the intent to close it and no expiry attached. A flag named for removal before production with no gate that blocks the release. A comment marking work to be finished with no owner and no failing check. Each is the identical mechanism: provisional intent recorded in a location no enforcement point reads. Each persists exactly as long as it functions, which is to say indefinitely. The medium changes. The mechanism does not.
Scale and time are the multipliers, and both are drawn from the same mechanism rather than added to it. Every consumer that adopts functioning code treats it as settled, because functioning is the only property adoption can measure. None inherit the caveat, because the caveat was never encoded in what they pulled. Each adoption raises the cost of removal, and each year raises the number of dependents. The exposure is that provisional code accumulates dependents silently and converts, without any decision being made, into an interface others require. The debt does not stay with the author. It distributes across every party that never agreed to hold it.
The distinction between temporary and permanent code does not exist at runtime. It exists only where enforcement lives. If a fix has to expire, the expiry must be a control the system executes: a hard stop, a dependency that refuses to load, a gate that blocks release until the replacement ships. Absent that control, the honest classification is permanent, and the code must be built, reviewed, and owned as permanent from the first commit. There is no third state. There is code the system will retire on its own and code it will not.
A label is not a control. Twelve years and nearly 20 million installs is the measured cost of treating a naming convention as a lifecycle. The deprecation issued today is a manual action, the first enforcement applied to this fix in that entire span. It functions only because a person remembered the original intent and chose to act on it. That mechanism does not scale to 20 million installs. It did not scale here, which is why the action arrives twelve years late.
What must now be true is narrow and non-negotiable. Provisional code carries its own kill condition at write time or it is not provisional. The follow-up has an owner and a gate, or it does not exist as a plan and should not be recorded as one. Any consumer running code marked temporary should read that mark as absent, because the runtime already does. Whether a maintained replacement exists for every affected install is not confirmed, and that uncertainty is the same missing enforcement surfacing one more time, now distributed across every system that pulled the fix and named by no one until today.
Keep Reading
software supply chainNobody checked what came back
The idTech build did not fail. It resolved a version reference exactly as designed, treating a source's identity as proof of its content's integrity.
software supply chainThe unread binary every compiler still trusts
Translating rustc to C changes the compiler's substrate, not its logic of trust: build pipelines execute on resolved references, never verified content.
software supply chainA name is not trust
Package registries resolve a name to an artifact, not a source. When the party behind a trusted name changes, the system inherits trust it cannot verify.
Latest on the Wire
Full wire →- Bend: a proof-checked language that aims to make AI coding bugs unmergeableHacker News
- Bonsai 2 27B: a 27B model in 5.9GB that keeps 98% of its benchmarksHacker News
- CrowdSec Confirms Private Source Code Leak Traced to Tanstack Supply-Chain BackdoorHacker News
- Unverifiable: no retrievable content for "Astra for Law"Hacker News
New signal daily · RSS
Stay in the loop
New writing delivered when it's ready. No schedule, no spam.