Normal view

There are new articles available, click to refresh the page.
Before yesterdayThe+Daily+WTF

CodeSOD: Asynchronous Directories

9 September 2026 at 06:30

Eri has a mix of a "true confession" and a "wait, really?" today.

The programming language Vala bills itself as a C# like language that compiles into something pretty close to C performance, designed specifically for writing code against Gnome and its associated libraries.

One of the C#-isms in brings in is async/await type semantics. You can yield someAsyncFunction(), which returns control to the caller, allowing it to proceed until the yielded function returns an actual value.

Because it has asynchronous functions, many library functions for handling I/O are already async. So you can make_directory_async, which yields control so you can keep executing while waiting for the filesystem to make your directory.

There are also synchronous versions of those methods. And then there's create_directory_with_parents, which will create a chain of directories for you. That's the synchronous version, and Vala's core library has decided not to provide an asynchronous version of it, which is my "wait, really?" I suspect it's really about the race conditions involved and the risks of things going wrong while doing it asynchronously; all solvable problems, but tricky ones to solve.

But it's the problem Eri had, and this is their solution:

/// Note: does not throw if target already exists
async void create_directory_with_parents_async(File file, Cancellable? cancellable = null) throws Error {
	var to_create = new File[0];
	var? current_target = file;
	while(current_target != null) {
		try {
			yield current_target.make_directory_async(Priority.DEFAULT, cancellable);
		} catch(IOError.NOT_FOUND e) {
			to_create += current_target;
			current_target = current_target.get_parent();
			continue;
		} catch(IOError.EXISTS e) {
			break;
		}
		break;
	}

	for (int i = to_create.length - 1; i >= 0; --i) {
		try {
			yield to_create[i].make_directory_async(Priority.DEFAULT, cancellable);
		} catch(IOError.EXISTS e) {
			// Created by another process
		}
	}
}

If I'm reading this correctly, we start by trying to create the full path to our leaf node. If there's a not found error, we go up one level and try and create that one. We keep trying that until we either run out of parent nodes to try against, or we hit a directory that already exists, or we successfully create a directory. All along the way, we keep appending the current_target to our to_create array.

Once we've gotten that baseline, we then iterate across our to_create array, backwards, creating the shortest non-existent paths first.

This works, but it's ugly as sin. Mostly, it's ugly because we're using exceptions for flow control instead of doing things like checking for file existence, though I suppose those checks may also break our goal of doing all our I/O operations in an async context. I don't know enough about Vala to know the better way of doing this.

Eri writes:

The function works as intended, but trying to trace control flow through the first loop is not pleasant. Ironically, the C mechanism Vala wraps is slightly advanced error codes, which would be nicer to work with in this case

Eri also provides a slightly re-worked version of the main loop, that is at least a bit easier to follow, but still an ugly approach:

	while(current_target != null) {
		try {
			yield current_target.make_directory_async(Priority.DEFAULT, cancellable);
			break;
		} catch(IOError.EXISTS e) {
			break;
		} catch(IOError.NOT_FOUND e) {
			to_create += current_target;
			current_target = current_target.get_parent();
		}
	}

Still, since this is an attempt to patch over a missing core library method and solve a tricky problem about how to handle race conditions, I think absolution is reasonable. It's ugly, it's weird, but it does the job. Go hide it in a box, and never touch its implementation again- except to make it go away.

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

A Mortal Blow

8 September 2026 at 06:30

From our anonymous submitter:

Having reached the end of the road at a company increasingly swallowed up companies further east which you'd never believe were still afloat, I found myself headhunted for certain specialty software skills. I was reaching the final few years of my expected working span, so I jumped at the chance. The money was (to me at that time) spectacularly good, so I jumped into it.

Time Is Life, 1080×1920, 154.35 KB

It started when my first day was spent by me being sent home for the weeks it was still going to take to onboard me. Not bad, engaged to wait, as it were, and the first 6 months was thus and so.

The man who had interviewed me, call him Fred, was intelligent and urbane, and was a joy to meet. He and I clearly hit it off, and lo and behold I was in. It was he who gave me my first assignment, which was mathematical analysis of their core milk-cow program because they needed to find out what it did, and how it did it, so they could perhaps implement it in a more contemporary language.

So I did that, and was just about to publish my findings with him, when Fred inconveniently dropped dead suddenly. In the what-are-we-going-to-do-now-our-key-man-is-no-more confusion, we contractors were forgotten.

For the next 18 months or so (may have been more, may have been less) I was more or less ignored. I spent the time writing a development environment to work on any part of the program conveniently, all the while sitting next to a man who was constantly, forcefully and repetitiously speaking ill of the managers in his line structure. The ridiculously garrulous boss who inherited me thought little of me, and handed me the little work that came my way with active hostility. One or two good guys, but mostly a cabal of elderly men trying to preserve their little money-spinner as long as they could, and a johnny-come-lately trying to increase (and even introduce) automatic processes was less than welcome. During that time I spent quite some time on TDWTF, submitting a gem or two here and there.

No surprise when they finally kicked my arse away. No love lost there. Now working my last couple of years to retirement as a postie.

No punchline here. I want you to know that dropping dead from work is all too real and can happen to anyone.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

Best of…: Classic WTF: A Dumbain Specific Language

7 September 2026 at 06:30
It's a holiday here in the US, a celebration of labor, so we're reaching back through the archives for a story about an attempt to be labor saving that was not successful. Original. --Remy

I’ve had to write a few domain-specific-languages in the past. As per Remy’s Law of Requirements Gathering, it’s been mostly because the users needed an Excel-like formula language. The danger of DSLs, of course, is that they’re often YAGNI in the extreme, or at least a sign that you don’t really understand your problem.

XML, coupled with schemas, is a tool for building data-focused DSLs. If you have some complex structure, you can convert each of its features into an XML attribute. For example, if you had a grammar that looked something like this:

The Source specification obeys the following syntax

source = ( Feature1+Feature2+... ":" ) ? steps

Feature1 = "local" | "global"

Feature2 ="real" | "virtual" | "ComponentType.all"

Feature3 ="self" | "ancestors" | "descendants" | "Hierarchy.all"

Feature4 = "first" | "last" | "DayAllocation.all"

If features are specified, the order of features as given above has strictly to be followed.

steps = oneOrMoreNameSteps | zeroOrMoreNameSteps | componentSteps

oneOrMoreNameSteps = nameStep ( "." nameStep ) *

zeroOrMoreNameSteps = ( nameStep "." ) *

nameStep = "#" name

name is a string of characters from "A"-"Z", "a"-"z", "0"-"9", "-" and "_". No umlauts allowed, one character is minimum.

componentSteps is a list of valid values, see below.

Valid 'componentSteps' are:

- GlobalValue
- Product
- Product.Brand
- Product.Accommodation
- Product.Accommodation.SellingAccom
- Product.Accommodation.SellingAccom.Board
- Product.Accommodation.SellingAccom.Unit
- Product.Accommodation.SellingAccom.Unit.SellingUnit
- Product.OnewayFlight
- Product.OnewayFlight.BookingClass
- Product.ReturnFlight
- Product.ReturnFlight.BookingClass
- Product.ReturnFlight.Inbound
- Product.ReturnFlight.Outbound
- Product.Addon
- Product.Addon.Service
- Product.Addon.ServiceFeature

In addition to that all subsequent steps from the paths above are permitted, that is 'Board', 
'Accommodation.SellingAccom' or 'SellingAccom.Unit.SellingUnit'.
'Accommodation.Unit' in the contrary is not permitted, as here some intermediate steps are missing.

You could turn that grammar into an XML document by converting syntax elements to attributes and elements. You could do that, but Stella’s predecessor did not do that. That of course, would have been work, and they may have had to put some thought on how to relate their homebrew grammar to XSD rules, so instead they created an XML schema rule for SourceAttributeType that verifies that the data in the field is valid according to the grammar… using regular expressions. 1,310 characters of regular expressions.

<xs:simpleType>
    <xs:restriction base="xs:string">
            <xs:pattern value="(((Scope.)?(global|local|current)\+?)?((((ComponentType.)?
(real|virtual))|ComponentType.all)\+?)?((((Hierarchy.)?(self|ancestors|descendants))|Hierarchy.all)\+?)?
((((DayAllocation.)?(first|last))|DayAllocation.all)\+?)?:)?(#[A-Za-z0-9\-_]+(\.(#[A-Za-z0-9\-_]+))*|(#[A-Za-z0-
9\-_]+\.)*
(ThisComponent|GlobalValue|Product|Product\.Brand|Product\.Accommodation|Product\.Accommodation\.SellingAccom|Prod
uct\.Accommodation\.SellingAccom\.Board|Product\.Accommodation\.SellingAccom\.Unit|Product\.Accommodation\.Selling
Accom\.Unit\.SellingUnit|Product\.OnewayFlight|Product\.OnewayFlight\.BookingClass|Product\.ReturnFlight|Product\.
ReturnFlight\.BookingClass|Product\.ReturnFlight\.Inbound|Product\.ReturnFlight\.Outbound|Product\.Addon|Product\.
Addon\.Service|Product\.Addon\.ServiceFeature|Brand|Accommodation|Accommodation\.SellingAccom|Accommodation\.Selli
ngAccom\.Board|Accommodation\.SellingAccom\.Unit|Accommodation\.SellingAccom\.Unit\.SellingUnit|OnewayFlight|Onewa
yFlight\.BookingClass|ReturnFlight|ReturnFlight\.BookingClass|ReturnFlight\.Inbound|ReturnFlight\.Outbound|Addon|A
ddon\.Service|Addon\.ServiceFeature|SellingAccom|SellingAccom\.Board|SellingAccom\.Unit|SellingAccom\.Unit\.Sellin
gUnit|BookingClass|Inbound|Outbound|Service|ServiceFeature|Board|Unit|Unit\.SellingUnit|SellingUnit))"/>
    </xs:restriction>
</xs:simpleType>
</xs:union>

There’s a bug in that regex that Stella needed to fix. As she put it: “Every time you evaluate it a few little kitties die because you shouldn’t use kitties to polish your car. I’m so, so sorry, little kitties…”

The full, unexcerpted code is below, so… at least it has documentation. In two languages!

<xs:simpleType name="SourceAttributeType">
                <xs:annotation>
                        <xs:documentation xml:lang="de">
                Die Source Angabe folgt folgender Syntax

                        source = ( Eigenschaft1+Eigenschaft2+... ":" ) ? steps

                        Eigenschaft1 = "local" | "global"

                        Eigenschaft2 ="real" | "virtual" | "ComponentType.all"

                        Eigenschaft3 ="self" | "ancestors" | "descendants" | "Hierarchy.all"

                        Eigenschaft4 = "first" | "last" | "DayAllocation.all"

                        Falls Eigenschaften angegeben werden muss zwingend die oben angegebene Reihenfolge der Eigenschaften eingehalten werden.

                        steps = oneOrMoreNameSteps | zeroOrMoreNameSteps | componentSteps

                        oneOrMoreNameSteps = nameStep ( "." nameStep ) *

                        zeroOrMoreNameSteps = ( nameStep "." ) *

                        nameStep = "#" name

                        name ist eine Folge von Zeichen aus der Menge "A"-"Z", "a"-"z", "0"-"9", "-" und "_". Keine Umlaute. Mindestens ein Zeichen

                        componentSteps ist eine Liste gültiger Werte, siehe im folgenden

                Gültige 'componentSteps' sind zunächst:

                        - GlobalValue
                        - Product
                        - Product.Brand
                        - Product.Accommodation
                        - Product.Accommodation.SellingAccom
                        - Product.Accommodation.SellingAccom.Board
                        - Product.Accommodation.SellingAccom.Unit
                        - Product.Accommodation.SellingAccom.Unit.SellingUnit
                        - Product.OnewayFlight
                        - Product.OnewayFlight.BookingClass
                        - Product.ReturnFlight
                        - Product.ReturnFlight.BookingClass
                        - Product.ReturnFlight.Inbound
                        - Product.ReturnFlight.Outbound
                        - Product.Addon
                        - Product.Addon.Service
                        - Product.Addon.ServiceFeature

                Desweiteren sind alle Unterschrittfolgen aus obigen Pfaden erlaubt, also 'Board', 'Accommodation.SellingAccom' oder 'SellingAccom.Unit.SellingUnit'.
                'Accommodation.Unit' hingegen ist nicht erlaubt, da in diesem Fall einige Zwischenschritte fehlen.

                                </xs:documentation>
                        <xs:documentation xml:lang="en">
                                The Source specification obeys the following syntax

                                source = ( Feature1+Feature2+... ":" ) ? steps

                                Feature1 = "local" | "global"

                                Feature2 ="real" | "virtual" | "ComponentType.all"

                                Feature3 ="self" | "ancestors" | "descendants" | "Hierarchy.all"

                                Feature4 = "first" | "last" | "DayAllocation.all"

                                If features are specified, the order of features as given above has strictly to be followed.

                                steps = oneOrMoreNameSteps | zeroOrMoreNameSteps | componentSteps

                                oneOrMoreNameSteps = nameStep ( "." nameStep ) *

                                zeroOrMoreNameSteps = ( nameStep "." ) *

                                nameStep = "#" name

                                name is a string of characters from "A"-"Z", "a"-"z", "0"-"9", "-" and "_". No umlauts allowed, one character is minimum.

                                componentSteps is a list of valid values, see below.

                                Valid 'componentSteps' are:

                                - GlobalValue
                                - Product
                                - Product.Brand
                                - Product.Accommodation
                                - Product.Accommodation.SellingAccom
                                - Product.Accommodation.SellingAccom.Board
                                - Product.Accommodation.SellingAccom.Unit
                                - Product.Accommodation.SellingAccom.Unit.SellingUnit
                                - Product.OnewayFlight
                                - Product.OnewayFlight.BookingClass
                                - Product.ReturnFlight
                                - Product.ReturnFlight.BookingClass
                                - Product.ReturnFlight.Inbound
                                - Product.ReturnFlight.Outbound
                                - Product.Addon
                                - Product.Addon.Service
                                - Product.Addon.ServiceFeature

                                In addition to that all subsequent steps from the paths above are permitted, that is 'Board', 'Accommodation.SellingAccom' or 'SellingAccom.Unit.SellingUnit'.
                                'Accommodation.Unit' in the contrary is not permitted, as here some intermediate steps are missing.

                        </xs:documentation>
                </xs:annotation>
                <xs:union>
                        <xs:simpleType>
                                <xs:restriction base="xs:string">
                                        <xs:pattern value="(((Scope.)?(global|local|current)\+?)?((((ComponentType.)?(real|virtual))|ComponentType.all)\+?)?((((Hierarchy.)?(self|ancestors|descendants))|Hierarchy.all)\+?)?((((DayAllocation.)?(first|last))|DayAllocation.all)\+?)?:)?(#[A-Za-z0-9\-_]+(\.(#[A-Za-z0-9\-_]+))*|(#[A-Za-z0-9\-_]+\.)*(ThisComponent|GlobalValue|Product|Product\.Brand|Product\.Accommodation|Product\.Accommodation\.SellingAccom|Product\.Accommodation\.SellingAccom\.Board|Product\.Accommodation\.SellingAccom\.Unit|Product\.Accommodation\.SellingAccom\.Unit\.SellingUnit|Product\.OnewayFlight|Product\.OnewayFlight\.BookingClass|Product\.ReturnFlight|Product\.ReturnFlight\.BookingClass|Product\.ReturnFlight\.Inbound|Product\.ReturnFlight\.Outbound|Product\.Addon|Product\.Addon\.Service|Product\.Addon\.ServiceFeature|Brand|Accommodation|Accommodation\.SellingAccom|Accommodation\.SellingAccom\.Board|Accommodation\.SellingAccom\.Unit|Accommodation\.SellingAccom\.Unit\.SellingUnit|OnewayFlight|OnewayFlight\.BookingClass|ReturnFlight|ReturnFlight\.BookingClass|ReturnFlight\.Inbound|ReturnFlight\.Outbound|Addon|Addon\.Service|Addon\.ServiceFeature|SellingAccom|SellingAccom\.Board|SellingAccom\.Unit|SellingAccom\.Unit\.SellingUnit|BookingClass|Inbound|Outbound|Service|ServiceFeature|Board|Unit|Unit\.SellingUnit|SellingUnit))"/>
                                </xs:restriction>
                        </xs:simpleType>
                </xs:union>
</xs:simpleType>
[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

Error'd: Good Time

4 September 2026 at 06:30

Astute readers noticed last week that this editor (that is to say, me) had his own error'd failure to remember what day it was. Thank you for pointing it out promptly, and then proceeding to send in a bunch of examples of other sites calendar failures. Misery loves company!

Traveler's travails, from C_Chell "Trying to complete the form on https://www.ihg.com to tell when I plan to arrive at the hotel, I can't complete the form because of this little time problem."

06036a0253dc4d61b4bd648ab59c2dae

"You Have -1 Month(s) To Order!" announces dragoncoder047. "Ah, GradImages... the company that told all graduates that they'd get a free 5x7 but tried to charge me for it, then refused to honor my "unsubscribe" request and is *still* emailing me to this day... Can't do date math? Par for the course."

1820ae48b9df4a759ffbde45a8c715e0

"Stansted Temporal UI design" shared by Michael R. "While waiting for a friend to arrive at Stansted I see this. I better fire up the DeLorean to pick her up at 00:06 tomorrow."

8fa526db89fe46d88d6d2597fe0fa3ae

While he was hunting through the website, Michael R. also found that "The Stansted airport website seems to suffer from Directional Confusion."

89c37786ca724e82868eaab4f7285fcf

Nothing wrong with the calendar here, but Slaoput simply opposes mandatory existence. "I was filling out a form that said the Birthdate is optional, but when I hit submit I found out it was required. (I guess technically you have to be born to fill out the form.)"
NOT TO BE!

8bf00b3dc8ae4ca19481b42b9e63d0f4

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

CodeSOD: Heating Up

3 September 2026 at 06:30

A common option for retrofitting heating and cooling into older homes is a mini-split, frequently tied to a heat pump. They're (relatively) cheap to install, energy efficient, and can be added without substantial modifications to the home. They also, annoyingly, are mostly controlled via IR remotes, making them challenging to wire up to home automation or even a household thermostat.

People have made solutions, and today's code comes from one of those solutions. Which, I want to stress, this code comes from an open source project for home automation, so it's not the code that's wrong, here. At first I thought it was, and had a moment of, "I'm not going to pick on some hobby project," but then I realised the hobby project points at a deeper issue.

// temperature helper these are direct mappings based on the remote
float toFahrenheit(float fromCelsius) {
    // Lookup table for specific mappings
    const std::map<float, int> lookupTable = {
        {16.0, 61}, {16.5, 62}, {17.0, 63}, {17.5, 64}, {18.0, 65},
        {18.5, 66}, {19.0, 67}, {20.0, 68}, {21.0, 69}, {21.5, 70},
        {22.0, 71}, {22.5, 72}, {23.0, 73}, {23.5, 74}, {24.0, 75},
        {24.5, 76}, {25.0, 77}, {25.5, 78}, {26.0, 79}, {26.5, 80},
        {27.0, 81}, {27.5, 82}, {28.0, 83}, {28.5, 84}, {29.0, 85},
        {29.5, 86}, {30.0, 87}, {30.5, 88}
    };

    // Check if the input is in the lookup table
    auto it = lookupTable.find(fromCelsius);
    if (it != lookupTable.end()) {
        return it->second;
    }

    // Default conversion and rounding to nearest integer
    return roundf(fromCelsius * 1.8 + 32.0);
}

Okay, I am going to pick on their code a little bit; using float as a key in a map is asking for trouble, because rounding errors are going to surprise you. But honestly, failing to find the key you're looking for is better than the opposite, since that actually does the correct thing. Because if you look carefully at the table, you'll see that it's wrong.

18C, for example, should be 64F. Well, 64.4F, but we're rounding to an integer. The choice here is to roughly map every 0.5C increase to a 1F increase, which is not the conversion factor. They try and correct- note how the table mostly steps by 0.5C, but skips 19.5C.

The opposite direction is similarly bad:

// temperature helper these are direct mappings based on the remote
float toCelsius(float fromFahrenheit) {
    // Lookup table for specific mappings
    const std::map<int, float> lookupTable = {
        {61, 16.0}, {62, 16.5}, {63, 17.0}, {64, 17.5}, {65, 18.0},
        {66, 18.5}, {67, 19.0}, {68, 20.0}, {69, 21.0}, {70, 21.5},
        {71, 22.0}, {72, 22.5}, {73, 23.0}, {74, 23.5}, {75, 24.0},
        {76, 24.5}, {77, 25.0}, {78, 25.5}, {79, 26.0}, {80, 26.5},
        {81, 27.0}, {82, 27.5}, {83, 28.0}, {84, 28.5}, {85, 29.0},
        {86, 29.5}, {87, 30.0}, {88, 30.5}
    };

    // Check if the input is in the lookup table
    auto it = lookupTable.find(static_cast<int>(fromFahrenheit));
    if (it != lookupTable.end()) {
        return it->second;
    }

    // Default conversion and rounding to nearest 0.5
    return roundf((fromFahrenheit - 32.0) / 1.8 * 2) / 2.0;
}

Here, we can be off by as much as a 1C, which is certainly a noticeable feeling.

At first glance, I thought this was just a misguided attempt at optimizing the lookup. For common values, do a lookup instead of calculating because it's faster. Seems like the kind of mistake a hobby project might make, and definitely not a WTF. But it's the comment which corrects me: these are direct mappings based on the remote.

These remotes usually have a display. So when you see on the remote that you're trying to set the temperature to a comfortable 72F, the remote is actually sending 22.5C to the unit. That's the actual temperature being sent.

Now, why on Earth does the remote behave this way? Well, I haven't cracked one open to read off the part numbers, but I'm going to go out on a limb and guess that the microcontoller in the remote doesn't handle floating point operations all that well. So it almost certainly does use a lookup table to decide what signal to send, and the lookup table is populated by "good enough" approximations of temperature conversions. There aren't a lot of places that use Fahrenheit, so being "close enough" is a reasonable solution. If you want accurate temperatures, use SI units, not "freedom units".

In the end, I'd say that neither the hobby project, nor the remote control are the WTF here; locales that insist on using weird ass units are.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

What You Measure

2 September 2026 at 06:30

Rachel joined a new team which was proudly "metrics driven". When she first met with her boss, Zane, he explained his thinking.

"We need to be data-driven to make good decisions, right? We're a manufacturing company. We make widgets. At the end of the day, we need to make the most widgets for the lowest cost of goods sold. So we track that, and that feeds into every decision."

The team oversaw an automated production line, which meant the software was a mix of robotics, embedded firmware, high-level web based monitoring tools, and thickets of dreaded PLC code. And because you can't build an entire factory for test purposes, they only way they could test real-world scales with real-world data was to roll changes out to production. They could simulate, they could run tests on subsets of the system, but a change in the production line software couldn't truly be validated until it rolled out into the real world.

Rachel's first task on the new team involved making some changes to their metrics dashboard. It was viewed as a good way to get her feet wet with the new team. As it turned out, the metrics dashboard was a Google Sheet, with a complex series of formulas that involved multi-level INDEX functions- essentially querying the spreadsheets like they were a database. Why not use an actual database? Oh, they did — six actually — but the company obeyed Remy's Law of Requirements Gathering: "no matter what the requirements the users ask for, what they really wanted was Excel". The database data was pulled into the spreadsheet for reporting.

Now, a complicated sheet pulling in data from not one, but six different databases, they must have a pretty complex model to explain how changes to their software would impact productivity. And since they needed to model the software to make predictions about how it'd behave in production, that model must be extremely useful.

Of course it wasn't. The only metrics they tracked were output metrics, variations on "widgets produced per unit time". There were some performance metrics, so you could maybe potentially identify "oh, our overall throughput dropped because unit 5 became a bottleneck and started taking 1.5 extra seconds per widget", but nothing that actually helped you understand how the complex system made decisions. Or even why unit 5 was taking longer.

For example, there was an automated quality control scanner. It examined widgets as they came off the line, and rejected defective ones based on a computer vision algorithm. Did that subsystem record why it rejected a widget? No, it did not. The CV model was able to tag widgets with a defect category based on what it saw, but that information didn't get recorded anywhere. In fact, it didn't even record how many widgets got rejected. The only way to know was to have an operator on the assembly line count widgets in the bin manually. Since that ate up a bunch of an operator's time, it never happened unless the developers begged for it. And since the operator still couldn't answer the question "why was this widget rejected", it wasn't all that useful anyway.

Every change to the software was scored against the overall output metrics. This meant that when Rachel was ready to push out her first software change, something that would record how many widgets were rejected and why, whether or not it could be deployed was dependent on seeing the change improve, or at least not regress, the widgets-over-time scores. But the widgets-over-time were a noisy metric; it varied based on which operators were working any given shift, or based on supply chain constraints. Or sometimes, based on when one of the machines was last calibrated- theoretically something that happened on a set schedule, but really was up to the operators. This meant the first three times Rachel rolled her code out for a test run, the metrics regressed. Nothing she changed should have impacted the metrics, but the metrics regressed due to environmental issues.

This meant making a simple change could take weeks, because you could only do final validation on the real system, which means you had to mark off a block of time for a test run, you could only run a handful of tests a day, and if metrics regressed you had to account for that before you could release the software for actual production use.

Over the first few months, Rachel added instrumentation to the code. Anything along the way to generating an output widget, she recorded. The hope was that once they had enough data, they could build a useful model of the system. Unfortunately, Zane had other ideas.

"So, you haven't improved our metrics," Zane said. "Which, I remind you, we're a metrics driven organization. Every change needs to improve our metrics."

"Sure, but I'm gathering more data so we have a better idea of what makes our metrics tick. We don't know why our system does some of the things it does, because we don't record any logging about the decisions it makes."

"Right, but we already gather the key metrics."

"But you don't gather the data that tells you why those metrics are what they are!"

"Sure," Zane said. "But those aren't our key metrics."

That, unfortunately for Rachel, was where things landed. Understanding their complex system was a low priority. Pushing top-level metrics without understanding what fed into them, that was the priority. That didn't mean Rachel was powerless: any time she made a change that she thought might help the top level metrics, she also made sure to add instrumentation that explained how that change behaved. It was the compromise that kept Zane happy: she released features that impacted the top-level metrics, but she also made the system more observable.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

Representative Line: So Much Room

1 September 2026 at 06:30

Today's representative comment ran out of room.

int maxLen = getColumnSize(session, "audit", "text_value1") - 16; // Leave some room for

No, it isn't continued on the next line and just got trimmed out, except perhaps by a careless merge. This is the entire comment.

Clearly, written by David Chase, the creator of "The Sopranos".

There are so many things we might be leaving room for. We could leave some room for dessert. Leave some room for activities. Leave some room for the holy spirit. Leave some room for improvisation.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

Tales from the World Cup

31 August 2026 at 06:30

All I can say in response to our anonymous submitter's story is, ALMOST?!

With the World Cup being hosted in North America this year, I remembered this story that happened back in 2014. At the time I was working in Brazil, for a company that builds software systems for public services. And, with the World Cup being hosted there, in came the opportunity for local agencies to invest in modernization, with pretty much a blank check to get new services, so long as it was deployed before the end of the World Cup. And so the sales people did what they did best, and went around trying to upsell whoever would be willing to buy — no matter our actual capacity for developing the things.

So it was that I was pulled into this new fancy digital system for the police force of a state capital. However, we had only about 4 engineers available, and what they sold was a project estimated for a team of 20, to be delivered in 3 months, with no room for delay. And it wasn't just our core C&D product, but this massive thing with customized public-facing websites, live tracking of the position of different police cars delivered to a tablet in each car, automated reporting, etc.

Germany and Argentina face off in the final of the World Cup 2014 -2014-07-13 (5)

First thing: We received a pile of 24 resumes, and were told to choose 16 of those. Maybe 3 were acceptable, but we had to waste 1 month hiring and onboarding 13 other people who were worse than useless. Classic man-month problem. We eventually had to tell management that nothing would be delivered this way, so they did the very best next thing: fly us to this other city, so we could work embedded there, in full crunch mode for the delivery. We pretty much worked 12+ hours a day, 7 days a week, for those next 2 weeks.

Another situation: they wanted this system where people could take a photo of an incident in progress, and submit via this app + website, to be verified by an operator in real-time. We nicknamed it the "dick-pic encyclopedia." Even worse, we only had the budget to run a single server, so this thing receiving public traffic would live in the same system that was tracking police car locations. Luckily they were convinced it was a bad idea so it was only ever online for a short period of time.

Next, was the police car tracking. This was done by a tablet installed in each car, which would be sending and receiving location information. But, 1 week before our deadline, we were hitting a serious bug: everything was working when we ran the tests ourselves, but the cops would report very weird bugs when testing it in the field. So we asked to do some field debugging, and I went on a ride-along. Things were working pretty much fine everywhere, so I asked to be taken to where he remembered seeing the tablets fail—to which the policeman just decides to drive off straight into one of the favelas around the city. I guess I can cross out "doing debugging in a police car passenger seat in a notoriously dangerous neighborhood" off my bucket list. Root cause: turns out cellphone connections would be pretty spotty in those areas, which we weren't handling properly.

Either way, we delivered something on time that was severely below spec, and very much over-budget. Company tried to squirm out of paying overtime (was told that we would gain "prestige" by doing those extra hours), but I put my foot down and left that job shortly after. Last I heard they actually got sued for this and a bunch of similar projects, and almost went under.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

Error'd: Hello, New Mexico!

28 August 2026 at 06:30

Peter G. shared with us yet another ordering bungled example of. "Should really say "please engage in an Easter egg hunt to find your language"."

3ccdf44218264528b28550518f7d6aea

"Google can't count" claimed Peter S.. It adds up. "Yet another proof that 0=1, this time from Google."

2d284d0f696d48669a9c59251ecf9bc0

"Thanks, Microsoft" groused Ivan "Ever since Microsoft ate university e-mail services worldwide and became responsible for major free software mailing lists, quality of service has been steadily dropping. In order to report delivery problems to Outlook, you need a Microsoft account. You're prevented from creating it at first because of "suspicious activity". Once you're in, the contact address is pre-filled for you with an invalid email. Once you fix that in the web developer toolbar, fuck you anyway! I think the form isn't actually expected to work; the fact that the request was submitted is an error. The only thing missing from the experience is the "beware of the leopard" sign."

ae9ba09cc9474a2f89b8358201b0419a

"Mango Math" needs a bit of money math for the rest of the world to understand. Michael R. muttered "I will buy it by the slice then." The joke here is on the tip of my tongue. Explainer: the new pence is one hundredth of the decimal pound. No shillings no more, decreps! At that ratio, 3p per slice of cheesecake would indeed be far less dear than four pounds for the whole thing, barring translucent slices. Alas, the reality is simply the boring fact that the price is 3p per gram. Not as funny but I'm chuckling imagining Michael's transparent serving of diet cheesecake. I'll leave it up to you to decide if a gram really counts as an "item".

bd6d8a538f7a45b2a81f8b52d425b180

Clint clucked "Got this email from Bigbadtoystore. Lots of links available for preorder!" I think the talented website builders behind the New Mexico DOT have been busy.

d7efd5aabe754173a54fccb84c60b942

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

CodeSOD: The Big Family

27 August 2026 at 06:30

Some time ago, Charles shared with us some awful PHP, aka the most common sort. Today's code sample is maybe a little too big to sum up, but I'll let Charles take a crack at it.

It's so bad that even analyzing and laughing at it feels impossible. But it’s so bad, I couldn’t not share it.

I’m the only one handling all the IT-related tasks at my company, and I don’t have anyone here to vent or laugh about this kind of thing with. So, I figured, why not share it here? I’m hoping it’ll provide at least a little bit of catharsis or some dark humor.

To make sure the confidentiality of the codebase was respected, I took the liberty of generalizing it. You might notice some inconsistencies, but that’s just me trying to keep things neutral while protecting the original structure and functionality. Apologies if it looks a bit patchy – the goal was to avoid revealing any specific details or sensitive code.

The whole block is north of 400 lines, and it's doing a lot. Or well, maybe it's not, as you'll see.

Let's star with the outermost layer.

$resm_data = $data_source->fetchData("group=" . $item_id);
foreach ($resm_data as $key => $value) {
// rest of the code here
}

We fetch data from a data source, presumably a database, passing our condition as a string, which reeks of probable SQL injection, but I don't know what library they're using. I also note they're using the key/value style of array iteration, but never actually check the key.

    $option_id = $value->option_id;
    $resm_details = $detail_source->fetch($option_id);
    if ($resm_details) {
        $label = $resm_details->{"label$lang"};
        $description = $resm_details->{"description$lang"};
        $category = $resm_details->category;

Nice little bit of "meta" programming to get their localization working, it'll fetch labelen or labelde as needed. Definitely not a horrible, dangerous way to solve that problem.

We use that again to get our currency figured out. That lets us do number formatting. So much number formatting code.

        if ($category == 0) {
            $cost = $resm_details->{"cost" . $currency};
            $child_cost = $resm_details->{"child_cost" . $currency};
            $cost_info =
                $mot[101] . " : +" . number_format($cost, 2, ",", " ") . $currency_symbol . " - " .
                $mot[102] . " : +" . number_format($child_cost, 2, ",", " ") . $currency_symbol;
        } elseif ($category == 1) {
            $cost = $resm_details->{"cost" . $currency};
            $child_cost = 0;
            $cost_info = $mot[101] . " : +" . number_format($cost, 2, ",", " ") . $currency_symbol;
        } elseif ($category == 2) {
            $cost = 0;
            $child_cost = $resm_details->{"child_cost" . $currency};
            $cost_info = $mot[102] . " : +" . number_format($child_cost, 2, ",", " ") . $currency_symbol;
        } elseif ($category == 4) {
            $cost = $resm_details->{"cost" . $currency};
            $child_cost = 0;
            $cost_info = $mot[500] . " : +" . number_format($cost, 2, ",", " ") . $currency_symbol;
        } elseif (
            $resm_details->{"cost" . $currency} == 0 and
            $resm_details->{"child_cost" . $currency} == 0
        ) {
            $cost = 0;
            $child_cost = 0;
            $cost_info = "";
        } else {
            $cost = $resm_details->{"cost" . $currency};
            $child_cost = 0;
            $cost_info = $mot[200] . " : +" . number_format($cost, 2, ",", " ") . $currency_symbol;
        }

What is the $mot array? Why am I jumping to seemingly random locations in that array? Clearly it contains some headers for our output.

Anyway, there's plenty of HTML string munging happening too, don't you worry.

        if ($location == 1) {
            $quantity_block =
                '<div class="quantity-container">
                    <input type="text" value="1" id="quantity-' . $option_id . '" class="qty-control" name="quantity" min="1" max="1">
                    <div class="increase button">+</div>
                    <div class="decrease button">-</div>
                </div>';
            $quantity = 1;
        } else {
            $quantity_block = "";
            $quantity = "all";
        }

And then there's this little treat for parsing the time stored in our database: $time_data = json_decode($resm_details->time_data); That tells me they're storing date times as strings, so that's fun.

There are also a couple more bon mots as they build a drop down list:

            $departure_select = '<option value="-1">' . $mot[300] . '</option>';
            $arrival_select = '<option value="-1">' . $mot[301] . '</option>';

And then there's this monstrosity:

        // Add the main option to the template
        $TEMPLATE->SET_BLOCK_VARIABLES("Block_OPTIONS", [
            "ID" => $option_id,
            "QUANTITY" => $quantity,
            "LABEL_CLASS" => $label_class,
            "ACTION_CLASS" => $action_class,
            "LABEL" => $label,
            "DESCRIPTION" => $description,
            "QUANTITY_BLOCK" => $quantity_block,
            "COST_INFO" => $cost_info,
            "HOST_COST_DISPLAY" => $host_cost_display,
            "COST" => $cost,
            "CHILD_COST" => $child_cost,
            "LOCATION" => $location,
            "CATEGORY" => $category,
            "HOST_CLASS" => $host_class,
            "EXTRA" => $extra,
            "HOST_EXTRA" => $host_extra,
            "TIME_CHECKED" => ($has_times == 1) ? 'selected="' . $option_id . '" checked="true"' : '',
            "TIME_BLOCK" => $time_block
        ]);

All the nonsense that we concatenate together above gets shoved into some sort of template. And after that, that's where the good stuff starts. Because guess what? We have to do the same thing for child items.

        $resm_children_data = $data_source->fetchData("parent=" . $option_id);
        foreach ($resm_children_data as $child_key => $child_value) {
            $child_option_id = $child_value->option_id;
            $resm_child_details = $detail_source->fetch($child_option_id);

That's right, it's the same block of code, not quite copy/pasted, since they needed to put the word child in everything.

                // Add the child option to the template
                $TEMPLATE->SET_BLOCK_VARIABLES("Block_OPTIONS.Block_OPTIONS_CHILD", [
                    "ID" => $child_option_id,
                    "QUANTITY" => $child_quantity,
                    "LABEL_CLASS" => $child_label_class,
                    "ACTION_CLASS" => $child_action_class,
                    "LABEL" => $child_label,
                    "DESCRIPTION" => $child_description,
                    "QUANTITY_BLOCK" => $child_quantity_block,
                    "COST_INFO" => $child_cost_info,
                    "HOST_COST_DISPLAY" => $child_host_cost_display,
                    "COST" => $child_cost,
                    "CHILD_COST" => $child_extra_cost,
                    "LOCATION" => $child_location,
                    "CATEGORY" => $child_category,
                    "HOST_CLASS" => $child_host_class,
                    "EXTRA" => $child_extra,
                    "HOST_EXTRA" => $child_host_extra,
                    "TIME_CHECKED" => ($child_has_times == 1) ? 'selected="' . $child_option_id . '" checked="true"' : '',
                    "TIME_BLOCK" => $child_time_block
                ]);

And now, if you liked the child record, guess what? Those children have got siblings. What can I say, it's a big family.

                $resm_sibling_data = $data_source->fetchData("parent=" . $parent_option_id);
                foreach ($resm_sibling_data as $sibling_key => $sibling_value) {
                    $sibling_option_id = $sibling_value->option_id;
                    $resm_sibling_details = $detail_source->fetch($sibling_option_id);

And that means, yes, we also use that template thing again:

                        // Add the sibling option to the template
                        $TEMPLATE->SET_BLOCK_VARIABLES("Block_OPTIONS.Block_OPTIONS_CHILD", [
                            "ID" => $sibling_option_id,
                            "QUANTITY" => $sibling_quantity,
                            "LABEL_CLASS" => $sibling_label_class,
                            "ACTION_CLASS" => $sibling_action_class,
                            "LABEL" => $sibling_label,
                            "DESCRIPTION" => $sibling_description,
                            "QUANTITY_BLOCK" => $sibling_quantity_block,
                            "COST_INFO" => $sibling_cost_info,
                            "HOST_COST_DISPLAY" => $sibling_host_cost_display,
                            "COST" => $sibling_cost,
                            "SIBLING_COST" => $sibling_extra_cost,
                            "LOCATION" => $sibling_location,
                            "CATEGORY" => $sibling_category,
                            "HOST_CLASS" => $sibling_host_class,
                            "EXTRA" => $sibling_extra,
                            "HOST_EXTRA" => $sibling_host_extra,
                            "TIME_CHECKED" => ($sibling_has_times == 1) ? 'selected="' . $sibling_option_id . '" checked="true"' : '',
                            "TIME_BLOCK" => $sibling_time_block
                        ]);

Someday, I hope the person who wrote this learn about methods and function calls. Maybe they could write their own one day.

In any case, here's the whole thing:

$resm_data = $data_source->fetchData("group=" . $item_id);
foreach ($resm_data as $key => $value) {
    $option_id = $value->option_id;
    $resm_details = $detail_source->fetch($option_id);
    if ($resm_details) {
        $label = $resm_details->{"label$lang"};
        $description = $resm_details->{"description$lang"};
        $category = $resm_details->category;

        // Process cost based on category
        if ($category == 0) {
            $cost = $resm_details->{"cost" . $currency};
            $child_cost = $resm_details->{"child_cost" . $currency};
            $cost_info =
                $mot[101] . " : +" . number_format($cost, 2, ",", " ") . $currency_symbol . " - " .
                $mot[102] . " : +" . number_format($child_cost, 2, ",", " ") . $currency_symbol;
        } elseif ($category == 1) {
            $cost = $resm_details->{"cost" . $currency};
            $child_cost = 0;
            $cost_info = $mot[101] . " : +" . number_format($cost, 2, ",", " ") . $currency_symbol;
        } elseif ($category == 2) {
            $cost = 0;
            $child_cost = $resm_details->{"child_cost" . $currency};
            $cost_info = $mot[102] . " : +" . number_format($child_cost, 2, ",", " ") . $currency_symbol;
        } elseif ($category == 4) {
            $cost = $resm_details->{"cost" . $currency};
            $child_cost = 0;
            $cost_info = $mot[500] . " : +" . number_format($cost, 2, ",", " ") . $currency_symbol;
        } elseif (
            $resm_details->{"cost" . $currency} == 0 and
            $resm_details->{"child_cost" . $currency} == 0
        ) {
            $cost = 0;
            $child_cost = 0;
            $cost_info = "";
        } else {
            $cost = $resm_details->{"cost" . $currency};
            $child_cost = 0;
            $cost_info = $mot[200] . " : +" . number_format($cost, 2, ",", " ") . $currency_symbol;
        }

        $location = $resm_details->location;
        $label_class = $location == 1 ? "has-quantity" : "no-quantity";
        $action_class = $location == 1 ? "active-with-quantity" : "active-no-quantity";
        if ($location == 1) {
            $quantity_block =
                '<div class="quantity-container">
                    <input type="text" value="1" id="quantity-' . $option_id . '" class="qty-control" name="quantity" min="1" max="1">
                    <div class="increase button">+</div>
                    <div class="decrease button">-</div>
                </div>';
            $quantity = 1;
        } else {
            $quantity_block = "";
            $quantity = "all";
        }

        $has_times = $resm_details->has_times;

        if ($has_times == 1) {
            $time_counter++;
            $time_data = json_decode($resm_details->time_data);

            $departure_select = '<option value="-1">' . $mot[300] . '</option>';
            $arrival_select = '<option value="-1">' . $mot[301] . '</option>';

            $departures = $time_data->departures;
            $arrivals = $time_data->arrivals;

            foreach ($departures as $dep_key => $departure) {
                $departure_select .= '<option value="' . $dep_key . '">' . $departure . '</option>';
            }

            foreach ($arrivals as $arr_key => $arrival) {
                $arrival_select .= '<option value="' . $arr_key . '">' . $arrival . '</option>';
            }

            $departure_block = '<div class="col-half time-select-' . $option_id . '" style="padding-right: 0;">
                <select class="form-control time-departure" style="text-align: center;">' . $departure_select . '</select>
                </div>';

            $arrival_block = '<div class="col-half time-select-' . $option_id . '" style="padding-left: 0;">
                <select class="form-control time-arrival" style="text-align: center;">' . $arrival_select . '</select>
                </div>';

            $time_block = '<div class="row time-container">
                ' . $departure_block . $arrival_block . '
            </div><small class="error-message time-error">' . $mot[302] . '</small>';
        } else {
            $time_block = '';
        }

        $host_cost_display = "";
        $extra = $resm_details->extra;
        $host_extra = $resm_details->host_extra;
        $host_class = "";

        if ($extra == 1) {
            $extra_cost_1 = $resm_details->{"cost" . $currency . "_1"};
            $extra_cost_2 = $resm_details->{"cost" . $currency . "_2"};

            $default_extra = $host_price == 0 ? $extra_cost_2 : $extra_cost_1;
            $cost_info = $mot[101] . ' : +<span class="extra-cost">' . number_format($default_extra, 2, ",", " ") .
                "</span>" . $currency_symbol . " - " . $mot[102] . ' : +<span class="child-cost">0.00</span>' . $currency_symbol;

            $host_class = " extra-option";
        }
        if ($host_extra == 1) {
            $host_cost_display =
                '<span class="host-cost">' . $mot[500] . ' : +<span class="host-price">0.00</span>' . $currency_symbol .
                "</span><br>";
        }

        // Add the main option to the template
        $TEMPLATE->SET_BLOCK_VARIABLES("Block_OPTIONS", [
            "ID" => $option_id,
            "QUANTITY" => $quantity,
            "LABEL_CLASS" => $label_class,
            "ACTION_CLASS" => $action_class,
            "LABEL" => $label,
            "DESCRIPTION" => $description,
            "QUANTITY_BLOCK" => $quantity_block,
            "COST_INFO" => $cost_info,
            "HOST_COST_DISPLAY" => $host_cost_display,
            "COST" => $cost,
            "CHILD_COST" => $child_cost,
            "LOCATION" => $location,
            "CATEGORY" => $category,
            "HOST_CLASS" => $host_class,
            "EXTRA" => $extra,
            "HOST_EXTRA" => $host_extra,
            "TIME_CHECKED" => ($has_times == 1) ? 'selected="' . $option_id . '" checked="true"' : '',
            "TIME_BLOCK" => $time_block
        ]);

        $resm_children_data = $data_source->fetchData("parent=" . $option_id);
        foreach ($resm_children_data as $child_key => $child_value) {
            $child_option_id = $child_value->option_id;
            $resm_child_details = $detail_source->fetch($child_option_id);
            if ($resm_child_details) {
                $child_label = $resm_child_details->{"label$lang"};
                $child_description = $resm_child_details->{"description$lang"};
                $child_category = $resm_child_details->category;

                // Process cost for child category
                if ($child_category == 0) {
                    $child_cost = $resm_child_details->{"cost" . $currency};
                    $child_extra_cost = $resm_child_details->{"child_cost" . $currency};
                    $child_cost_info =
                        $mot[101] . " : +" . number_format($child_cost, 2, ",", " ") . $currency_symbol . " - " .
                        $mot[102] . " : +" . number_format($child_extra_cost, 2, ",", " ") . $currency_symbol;
                } elseif ($child_category == 1) {
                    $child_cost = $resm_child_details->{"cost" . $currency};
                    $child_extra_cost = 0;
                    $child_cost_info = $mot[101] . " : +" . number_format($child_cost, 2, ",", " ") . $currency_symbol;
                } elseif ($child_category == 2) {
                    $child_cost = 0;
                    $child_extra_cost = $resm_child_details->{"child_cost" . $currency};
                    $child_cost_info = $mot[102] . " : +" . number_format($child_extra_cost, 2, ",", " ") . $currency_symbol;
                } elseif ($child_category == 4) {
                    $child_cost = $resm_child_details->{"cost" . $currency};
                    $child_extra_cost = 0;
                    $child_cost_info = $mot[500] . " : +" . number_format($child_cost, 2, ",", " ") . $currency_symbol;
                } elseif (
                    $resm_child_details->{"cost" . $currency} == 0 and
                    $resm_child_details->{"child_cost" . $currency} == 0
                ) {
                    $child_cost = 0;
                    $child_extra_cost = 0;
                    $child_cost_info = "";
                } else {
                    $child_cost = $resm_child_details->{"cost" . $currency};
                    $child_extra_cost = 0;
                    $child_cost_info = $mot[200] . " : +" . number_format($child_cost, 2, ",", " ") . $currency_symbol;
                }

                $child_location = $resm_child_details->location;
                $child_label_class = $child_location == 1 ? "has-quantity" : "no-quantity";
                $child_action_class = $child_location == 1 ? "active-with-quantity" : "active-no-quantity";
                if ($child_location == 1) {
                    $child_quantity_block =
                        '<div class="quantity-container">
                    <input type="text" value="1" id="child-quantity-' . $child_option_id . '" class="qty-control" name="quantity" min="1" max="1">
                    <div class="increase button">+</div>
                    <div class="decrease button">-</div>
                </div>';
                    $child_quantity = 1;
                } else {
                    $child_quantity_block = "";
                    $child_quantity = "all";
                }

                $child_has_times = $resm_child_details->has_times;

                if ($child_has_times == 1) {
                    $child_time_counter++;
                    $child_time_data = json_decode($resm_child_details->time_data);

                    $child_departure_select = '<option value="-1">' . $mot[300] . '</option>';
                    $child_arrival_select = '<option value="-1">' . $mot[301] . '</option>';

                    $child_departures = $child_time_data->departures;
                    $child_arrivals = $child_time_data->arrivals;

                    foreach ($child_departures as $child_dep_key => $child_departure) {
                        $child_departure_select .= '<option value="' . $child_dep_key . '">' . $child_departure . '</option>';
                    }

                    foreach ($child_arrivals as $child_arr_key => $child_arrival) {
                        $child_arrival_select .= '<option value="' . $child_arr_key . '">' . $child_arrival . '</option>';
                    }

                    $child_departure_block = '<div class="col-half time-select-' . $child_option_id . '" style="padding-right: 0;">
                <select class="form-control time-departure" style="text-align: center;">' . $child_departure_select . '</select>
                </div>';

                    $child_arrival_block = '<div class="col-half time-select-' . $child_option_id . '" style="padding-left: 0;">
                <select class="form-control time-arrival" style="text-align: center;">' . $child_arrival_select . '</select>
                </div>';

                    $child_time_block = '<div class="row time-container">
                ' . $child_departure_block . $child_arrival_block . '
            </div><small class="error-message time-error">' . $mot[302] . '</small>';
                } else {
                    $child_time_block = '';
                }

                $child_host_cost_display = "";
                $child_extra = $resm_child_details->extra;
                $child_host_extra = $resm_child_details->host_extra;
                $child_host_class = "";

                if ($child_extra == 1) {
                    $child_extra_cost_1 = $resm_child_details->{"cost" . $currency . "_1"};
                    $child_extra_cost_2 = $resm_child_details->{"cost" . $currency . "_2"};

                    $default_child_extra = $host_price == 0 ? $child_extra_cost_2 : $child_extra_cost_1;
                    $child_cost_info = $mot[101] . ' : +<span class="extra-cost">' . number_format($default_child_extra, 2, ",", " ") .
                        "</span>" . $currency_symbol . " - " . $mot[102] . ' : +<span class="child-cost">0.00</span>' . $currency_symbol;

                    $child_host_class = " extra-option";
                }
                if ($child_host_extra == 1) {
                    $child_host_cost_display =
                        '<span class="host-cost">' . $mot[500] . ' : +<span class="host-price">0.00</span>' . $currency_symbol .
                        "</span><br>";
                }

                // Add the child option to the template
                $TEMPLATE->SET_BLOCK_VARIABLES("Block_OPTIONS.Block_OPTIONS_CHILD", [
                    "ID" => $child_option_id,
                    "QUANTITY" => $child_quantity,
                    "LABEL_CLASS" => $child_label_class,
                    "ACTION_CLASS" => $child_action_class,
                    "LABEL" => $child_label,
                    "DESCRIPTION" => $child_description,
                    "QUANTITY_BLOCK" => $child_quantity_block,
                    "COST_INFO" => $child_cost_info,
                    "HOST_COST_DISPLAY" => $child_host_cost_display,
                    "COST" => $child_cost,
                    "CHILD_COST" => $child_extra_cost,
                    "LOCATION" => $child_location,
                    "CATEGORY" => $child_category,
                    "HOST_CLASS" => $child_host_class,
                    "EXTRA" => $child_extra,
                    "HOST_EXTRA" => $child_host_extra,
                    "TIME_CHECKED" => ($child_has_times == 1) ? 'selected="' . $child_option_id . '" checked="true"' : '',
                    "TIME_BLOCK" => $child_time_block
                ]);

                $resm_sibling_data = $data_source->fetchData("parent=" . $parent_option_id);
                foreach ($resm_sibling_data as $sibling_key => $sibling_value) {
                    $sibling_option_id = $sibling_value->option_id;
                    $resm_sibling_details = $detail_source->fetch($sibling_option_id);
                    if ($resm_sibling_details) {
                        $sibling_label = $resm_sibling_details->{"label$lang"};
                        $sibling_description = $resm_sibling_details->{"description$lang"};
                        $sibling_category = $resm_sibling_details->category;

                        // Process cost for sibling category
                        if ($sibling_category == 0) {
                            $sibling_cost = $resm_sibling_details->{"cost" . $currency};
                            $sibling_extra_cost = $resm_sibling_details->{"sibling_cost" . $currency};
                            $sibling_cost_info =
                                $mot[101] . " : +" . number_format($sibling_cost, 2, ",", " ") . $currency_symbol . " - " .
                                $mot[102] . " : +" . number_format($sibling_extra_cost, 2, ",", " ") . $currency_symbol;
                        } elseif ($sibling_category == 1) {
                            $sibling_cost = $resm_sibling_details->{"cost" . $currency};
                            $sibling_extra_cost = 0;
                            $sibling_cost_info = $mot[101] . " : +" . number_format($sibling_cost, 2, ",", " ") . $currency_symbol;
                        } elseif ($sibling_category == 2) {
                            $sibling_cost = 0;
                            $sibling_extra_cost = $resm_sibling_details->{"sibling_cost" . $currency};
                            $sibling_cost_info = $mot[102] . " : +" . number_format($sibling_extra_cost, 2, ",", " ") . $currency_symbol;
                        } elseif ($sibling_category == 4) {
                            $sibling_cost = $resm_sibling_details->{"cost" . $currency};
                            $sibling_extra_cost = 0;
                            $sibling_cost_info = $mot[500] . " : +" . number_format($sibling_cost, 2, ",", " ") . $currency_symbol;
                        } elseif (
                            $resm_sibling_details->{"cost" . $currency} == 0 and
                            $resm_sibling_details->{"sibling_cost" . $currency} == 0
                        ) {
                            $sibling_cost = 0;
                            $sibling_extra_cost = 0;
                            $sibling_cost_info = "";
                        } else {
                            $sibling_cost = $resm_sibling_details->{"cost" . $currency};
                            $sibling_extra_cost = 0;
                            $sibling_cost_info = $mot[200] . " : +" . number_format($sibling_cost, 2, ",", " ") . $currency_symbol;
                        }

                        $sibling_location = $resm_sibling_details->location;
                        $sibling_label_class = $sibling_location == 1 ? "has-quantity" : "no-quantity";
                        $sibling_action_class = $sibling_location == 1 ? "active-with-quantity" : "active-no-quantity";
                        if ($sibling_location == 1) {
                            $sibling_quantity_block =
                                '<div class="quantity-container">
                    <input type="text" value="1" id="sibling-quantity-' . $sibling_option_id . '" class="qty-control" name="quantity" min="1" max="1">
                    <div class="increase button">+</div>
                    <div class="decrease button">-</div>
                </div>';
                            $sibling_quantity = 1;
                        } else {
                            $sibling_quantity_block = "";
                            $sibling_quantity = "all";
                        }

                        $sibling_has_times = $resm_sibling_details->has_times;

                        if ($sibling_has_times == 1) {
                            $sibling_time_counter++;
                            $sibling_time_data = json_decode($resm_sibling_details->time_data);

                            $sibling_departure_select = '<option value="-1">' . $mot[300] . '</option>';
                            $sibling_arrival_select = '<option value="-1">' . $mot[301] . '</option>';

                            $sibling_departures = $sibling_time_data->departures;
                            $sibling_arrivals = $sibling_time_data->arrivals;

                            foreach ($sibling_departures as $sibling_dep_key => $sibling_departure) {
                                $sibling_departure_select .= '<option value="' . $sibling_dep_key . '">' . $sibling_departure . '</option>';
                            }

                            foreach ($sibling_arrivals as $sibling_arr_key => $sibling_arrival) {
                                $sibling_arrival_select .= '<option value="' . $sibling_arr_key . '">' . $sibling_arrival . '</option>';
                            }

                            $sibling_departure_block = '<div class="col-half time-select-' . $sibling_option_id . '" style="padding-right: 0;">
                <select class="form-control time-departure" style="text-align: center;">' . $sibling_departure_select . '</select>
                </div>';

                            $sibling_arrival_block = '<div class="col-half time-select-' . $sibling_option_id . '" style="padding-left: 0;">
                <select class="form-control time-arrival" style="text-align: center;">' . $sibling_arrival_select . '</select>
                </div>';

                            $sibling_time_block = '<div class="row time-container">
                ' . $sibling_departure_block . $sibling_arrival_block . '
            </div><small class="error-message time-error">' . $mot[302] . '</small>';
                        } else {
                            $sibling_time_block = '';
                        }

                        $sibling_host_cost_display = "";
                        $sibling_extra = $resm_sibling_details->extra;
                        $sibling_host_extra = $resm_sibling_details->host_extra;
                        $sibling_host_class = "";

                        if ($sibling_extra == 1) {
                            $sibling_extra_cost_1 = $resm_sibling_details->{"cost" . $currency . "_1"};
                            $sibling_extra_cost_2 = $resm_sibling_details->{"cost" . $currency . "_2"};

                            $default_sibling_extra = $host_price == 0 ? $sibling_extra_cost_2 : $sibling_extra_cost_1;
                            $sibling_cost_info = $mot[101] . ' : +<span class="extra-cost">' . number_format($default_sibling_extra, 2, ",", " ") .
                                "</span>" . $currency_symbol . " - " . $mot[102] . ' : +<span class="sibling-cost">0.00</span>' . $currency_symbol;

                            $sibling_host_class = " extra-option";
                        }
                        if ($sibling_host_extra == 1) {
                            $sibling_host_cost_display =
                                '<span class="host-cost">' . $mot[500] . ' : +<span class="host-price">0.00</span>' . $currency_symbol .
                                "</span><br>";
                        }

                        // Add the sibling option to the template
                        $TEMPLATE->SET_BLOCK_VARIABLES("Block_OPTIONS.Block_OPTIONS_CHILD", [
                            "ID" => $sibling_option_id,
                            "QUANTITY" => $sibling_quantity,
                            "LABEL_CLASS" => $sibling_label_class,
                            "ACTION_CLASS" => $sibling_action_class,
                            "LABEL" => $sibling_label,
                            "DESCRIPTION" => $sibling_description,
                            "QUANTITY_BLOCK" => $sibling_quantity_block,
                            "COST_INFO" => $sibling_cost_info,
                            "HOST_COST_DISPLAY" => $sibling_host_cost_display,
                            "COST" => $sibling_cost,
                            "SIBLING_COST" => $sibling_extra_cost,
                            "LOCATION" => $sibling_location,
                            "CATEGORY" => $sibling_category,
                            "HOST_CLASS" => $sibling_host_class,
                            "EXTRA" => $sibling_extra,
                            "HOST_EXTRA" => $sibling_host_extra,
                            "TIME_CHECKED" => ($sibling_has_times == 1) ? 'selected="' . $sibling_option_id . '" checked="true"' : '',
                            "TIME_BLOCK" => $sibling_time_block
                        ]);
                    }
                }
            }
        }
    }
}
[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

CodeSOD: Lock 'Em Dead

26 August 2026 at 06:30

Kevin sends us an exception handler from C++. Let's see if we can spot what's going wrong:

catch (Exception::Deadlock)
{
   retry;
}

When we catch a deadlock happening, we retry. That's not a keyword in C++, and looking at how it's used, it has to be some kind of macro, and I suspect that the macro is hiding a goto underneath it.

The real problem, though, is that we suspect we're in a deadlock situation. That means this thread is waiting on a resource held by another thread which is waiting for a resource held by this thread. Neither train may continue until the other has passed. So this retry only works if it releases the resource held by this thread (letting the deadlocking thread proceed). But does it?

Not according ot Kevin. The code already had a pile of deadlocks in it, so they brought in a highly paid consultant to try and fix them by reordering access and tracing where mutexes were causing issues. This retry just jumps back up to the top of the block, without releasing any resources. It "seems the consultant wanted to add some deadlocks of their own," Kevin says.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

Representative Line: Both Ways Bug Me

25 August 2026 at 06:30

There are many cases where some sort of debugging block sneaks by, especially cases where we see preprocessors or templates working, which leave us with nonsense like if (true == false) running in production. But Codemonkey found a new twist on that sort of thing, in a SQL query being run in production.

WHERE (some conditions) AND (1 = 0 OR (1 = 1 AND (other conditions)))

The OR means that by twiddling the first equality check, we can toggle "always return rows" with "return based on condition". Toggling the second we can make it "never return rows", which I'm not certain is actually useful. I can see how these likely did start life as debugging flags, but they're still weird, still unnatural. They point to some other problem in observability in the code. And, as all "good" flags go, they're not documented anywhere, this seems like it started life as a query an analyst was running until it got turned into stored procedure to be run again and again. The flags have never been changed since the code was released, as far as anyone can tell.

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

RCE As a Feature

24 August 2026 at 06:30

The opposite of meritocracy is kakistocracy: the worst and least-qualified are the ones who rise to the top.

Get real familiar with that word, dear readers. I think you'll need it.

If anyone can back me up on this, it's our submitter, Jared B:

Time Magazine cover April 3, 2017

I am a teacher by profession, and worked for a year at an ed-tech company founded by a mechanical engineering professor, Harry. Harry had spent a great deal of time in the 90's developing a C interpreter (yes, you read that right). 30 years later, he remained convinced that his interpreter was the technology of the future, and had founded a company that offered math and computer science curriculum to K-12 students based on C programming.

Originally, he had written a textbook that introduced students to programming using a locally-installed version of his interpreter and custom IDE. A little vain, but no serious problems. As Chromebooks grew popular in schools, he had developed a web IDE where students could write and run C code.

But Harry could never give fully give up on the Windows IDE for his C interpreter. So, he included in the web version a "Run Locally" button for those school computers still running Windows. It worked like so: installing the interpreter and IDE locally would also install a daemon that activated on startup and ran a websocket server. This server had an endpoint which accepted as a parameter a string of C code. It would then pass this C code to the locally-installed interpreter to run.

As you might suspect, there was no authentication whatsoever on this local websocket server. Knowing the form of the protocol, ANY domain could connect to localhost:12345/execute_c_program and send arbitrary code to run (of course, Harry prided himself on the completeness of his C implementation, including execv() and the like). Trick a user into visiting a malicious website, and you automatically had RCE on their computer.

Adding insult to injury, I discovered that the server was bound to 0.0.0.0 so that if you had Harry's software (/malware) installed, any computer on the same network as you could send you arbitrary C code to execute without question.

These vulnerabilities had existed for several years before I joined the company. In all that time, Harry had never hired anybody but his own grad students as software developers, and none of them had noticed the problem. By that time, the software was installed on thousands of school-owned computers throughout the state.

I documented and demonstrated the vulnerabilities to Harry. He did release a new version of the software addressing the issues and citing "security improvements" in the release notes, but there was never a communication to school/district IT leaders to describe the importance of updating. I suspect that Harry should be in serious legal trouble for potentially compromising data related to schools and minors, but I've since moved on and dropped the subject.

During the year I spent at the company (not in any sense as a dev, mind you, but as a lowly curriculum writer), I also discovered and reported a cookie-stealing exploit that would have compromised student and teacher data, as well as a code injection on another of Harry's websites (he decided to demonstrate that his C interpreter could work as a web server via a page where a user could type a math expression, which was then eval()ed server-side without any sanitation). The latter vulnerability gave me remote access, where I discovered thousands of transaction records that included credit card information stored in the clear.

Harry's company is still in business to this day, and has recently been ranked in TIME's list of top American ed-tech companies. Oh, and the office router's admin page still had the default Google-able username and password, but that one's a freebie.

I knew someone like this once, only they were stuck on ColdFusion long after everyone else stopped caring about it. However, I don't think they went on to endanger an entire state's educational system, only to be lauded as a visionary leader. Can't say for sure, though.

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

Error'd: Failure, After Failure, After Failure...

21 August 2026 at 06:30

We have a couple from Foo (AKA Foo) today, include a special text copy-paste

Foo shared "I know you usually post image WTFs here, but here's a text output from chromium:

[...:ERROR:components/viz/service/display/display.cc:273] Frame latency is negative: -0.18 ms

While this issue might be fixed by now, at least on some platforms, I think it's remarkable that someone actually wrote this message without wondering if it ever makes sense ...

And also commented "I visited Spain to see the eclipse (which was great BTW). I had heard that temperature may drop during totality, but was surprised by how much." Negative Infinity!

0be1027004e44b13bd405c187d01c644

"Youfailedatmathtube" muttered dragoncoder047, snarking only "Title."

afc0394174854c19aaa86bbee370f978

"Hello to you too, New Mexico!" enthused Chris A. "Setting up web sites is hard. The DOT got bored half way through and just left the rest of the buttons as they were."

fbe8150c963d4abaac4897bf083e1992

Finally, "Failure Fail" from Basti "Did I succeed or did I fail? Is my whole life a success? Or a failure? I'm confused. You can find this here.

fc74a13838f6458587db51bd80405415

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

Representative Line: We All Register This

20 August 2026 at 06:30

Today's maybe more of a "representative data sheet entry" than anything else.

Every developer has the experience of reading the documentation. If you've been at this for some time, you've probably read bad documentation. Documentation that is incomplete, inaccurate, or otherwise flawed. Or, my personal favorite, the brief time where Oracle tried to put all of its documentation into an Adobe Flex site (aka, a Flash application, not a real web app). That one had fun bonus features, like "breaking copy and paste" and "preventing you from deep linking to a piece of the documentation".

But software documentation has got nothing on bad data sheets. When you buy an integrated chip from a vendor, whether it's a microcontroller that'll run your code, a sensor you're trying to get data from, you're at the mercy of the datasheet for understanding how it works. Sometimes, even finding an English language datasheet can be a challenge. The more complex the chip you're trying to interact with, the more complex the datasheet needs to be, and at a certain point, a lot of vendors say, "meh, you'll figure it out." I've had chips where the datasheet and reality disagreed about what registers were available, which often means that core functions of the chip require twiddling undocumented registers. For more fun, they sometimes lie about which pins on the chip do which thing, including mislabeling which pins handle power. There's nothing more fun than the tiny little "pop" of a chip dying when you throw 5V power onto a pin that's actually ground.

Now, there are some vendors, and some products, where the datasheets are pretty solid. This isn't a universal problem, but when you're working in an embedded space, "cheapest" is frequently the main criteria for picking components, and "cheapest" means "worst documented".

Which brings us to Jarek's recent experience going through a data sheet. The chip in question had a "fantastic feature" that would change how debugging worked, which was for "super users" to enable by setting a register.

3.2.4 Super User Fantastic Feature Enable Register
The Super User Fantastic Feature Enable Register allows the user to modify the behavior of the mEDBG.
Name: SUFFER
Offset:  0x0120
Reset: 0xFF

Sometimes, doing embedded work definitely feels like the SUFFER register is set.

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

CodeSOD: Back to the Lab

19 August 2026 at 06:30

Matlab is special. Scientists and researchers love it. Programmers hate it, and not just because it uses 1-based arrays. I've worked on a number of projects where the task was "take this Matlab code and convert it to C so we can run it on an embedded CPU". Somehow, in that process, I've avoided learning much about Matlab.

Andre works on a team that uses Matlab to manage experimental scenarios. They wanted to do a simple task: generate a set of participant-specific images, store them in a database, and reference them later. Somewhere in the intersection of the database product they were using, the Matlab license they had, and other constraints, they discovered that there simply was no good way to do this.

Enter "Jude". Jude said, "Don't worry about it, I can hack something together."

I present the code in its entirety, but don't ask me to explain it. Instead, read the comments.

nMk = 1;%counting non-response triggers, this cycles with each trial
nPress = 0;%counting button presses, noting the position in the log

for v = 1:height(resVmrk)%read each trigger
	switch nMk
		%it's kinda roundabout, but the only recognisable part is the response
		%yet I refer to it only by elision
		%and instead count the stimuli to reconstruct the pattern
		case 1%an almost reliable stimulus
			nPress = nPress+1;%trial start
			if strcmp(resVmrk.TriggerCode{v},'S1')%it must be a non-response
				resVmrk.TriggerCode{v} = 'cross';%name it properly
				nMk = 2;%and expect the next one
			else%except when it is not
				resLog.miss(nPress) = 1;%then note it down as missed
				nMk = 0;%and skip to response
			end
			
		case 2%usually reliable
			if strcmp(resVmrk.TriggerCode{v},'S1')%if the face loaded successfully
				resVmrk.TriggerCode{v} = 'face';%note it
				nMk = 3;%and proceed accordingly
				if nPress<=height(resLog)%trailing triggers at the end should be ignored
					resLog.facePos(nPress) = v;%note the position
				end
			else%if it failed to load it is a response
				resVmrk.Dur(v-1:v+1) = 0;%mark the whole trial for deletion
				resLog.miss(nPress) = 1;%and note it down as missing the face
				nMk = 0;%and skip to response
			end
			
		case 3%this one is not reliable, and sometimes is duplicated instead of missing
			if strcmp(resVmrk.TriggerCode{v},'S1')%if it is present at all
				resVmrk.TriggerCode{v} = 'empty';%first name it
				if v<height(resVmrk)%if it is not a trailing trigger, since it'll break the check otherwise
					if ~strcmp(resVmrk.TriggerCode{v+1},'S1')%if the next trigger is a response
						nMk = 0;%all is fine and it didn't freak out, proceed to response
					else%otherwise
						nMk = 3;%just treat as a double
						%and then count how many excess triggers are actually here
						nExcess = 1;%definitely one here already
						while strcmp(resVmrk.TriggerCode{v+nExcess+1},'S1')
							nExcess = nExcess+1;%and everything until the response
						end
						resVmrk.Dur(v-2:v+nExcess+2) = 0;%then mark the whole trial for deletion
						%this overwrites the same positions several time, but the important part is to get the preceding two, because I don't know which one of them is correct one, so I delete the whole trial
						if nPress<=height(resLog)
							resLog.bad(nPress) = 1;%also note it down as borked
						end
					end
				end
			else
				nMk = 0;%if it didn't happen at all simply proceed to response
			end
			
		case 0%this one reliably follows the response, so I address the response by elision
			if strcmp(resVmrk.TriggerCode{v},'S1')%skip response itself
				resVmrk.TriggerCode{v} = 'blink';%note the only reliable non-response (always following the response)
				nMk = 1;%start the trial anew
				if nPress<=height(resLog)%if it is not a trailing trigger
					resLog.respPos(nPress) = v-1;%note down the response position
					if resLog.miss(nPress)==1%and if it's a response without a stimulus
						resVmrk.Dur(v-1:v) = 0;%mark it for deletion as well
					end
				end
			end
	end
end

Ah, the classic "for-case" antipattern. That's gross enough, but what the heck is happening inside each of those cases?

My personal favorite comment is this one: "%this overwrites the same positions several time, but the important part is to get the preceding two, because I don't know which one of them is correct one, so I delete the whole trial"

Now, you may suspect comments like "usually reliable" are about what we see in the dataset, but I'm not so certain. Andre writes:

After reverting the last discovered way for his creation to corrupt the data I was able to figure out that 20% of the logs provided corresponded to different (unknown) experiments altogether.

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

Floating Along

18 August 2026 at 06:30

Today's submitter John F. was migrating data from a Microsoft platform to a Microsoft platform, using Microsoft tools. Absolutely nothing could go wrong, right?

Right?

Lining up the decimal points

A few years ago, I was working on a migration. We had sold part of our business, and so we had to extract a whole bunch of customer documents and metadata to provide to the buyer. The documents were stored in SharePoint on-premises, so the first step was extracting the metadata and storing it in a SQL Server database.

A colleague had used Microsoft's ETL tool SSIS to get the process started, and it generated a database schema. But after taking over, I wanted to change to PowerShell for greater control. For speed reasons, I decided to use System.Data.SqlClient.SqlBulkCopy, and getting that going required making sure my PowerShell script had all the correct data types.

One of our fields was the customer number. Customer numbers were up to 10 digits, but the first two were usually 0. Now, I prefer storing customer numbers as text, but someone in the distant past thought, This is a number, and SharePoint has a Number field, so I will use that.

Under the hood, Number fields in SharePoint are actually Doubles. Using a Double to store something exact like a customer number is not really ideal, but double-precision is absolutely enough to represent 10 digit numbers accurately. So what went wrong?

Well, remember we used SSIS to create the original table schema in SQL Server. I then used this table schema to write my script. But it turns out that in SQL Server world, the double-precision type is called float. If you want single-precision, you have to say float(24). I didn't know this, and so when I saw the SQL Server column as a float, I entered float as the corresponding .Net type in my script.

Oops.

So numbers came out of SharePoint as double. They were then converted to float before being inserted into SQL Server. Almost all records were fine, but large customer numbers had their last few digits changed. Testers didn't notice, but fortunately someone picked it up in the full load. We had to generate a list of changed numbers to patch the data after the fact.

[Advertisement] Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready.Learn more.

The State of Ticketing

17 August 2026 at 06:30

Developing software can't simply be done with a text editor and a compiler. There are a variety of other tools we have to bring to bear that support our efforts and keep the team organized, like say, source control.

There are certain tools we all have to use that I would argue, nobody has actually make a version that's any good. Build tooling is one of my go-to examples: there are no good build systems, only build systems that are good enough for this task.

Another is ticket/task management. In fact, I'd go so far as to say, there are no good ticket management tools. Amongst the not good tools, I'd put Jira as one of the not goodest of all.

What makes Jira attractive to companies is the same thing that makes it miserable, and the thing that infects any "enterprise" software platform and turns it into garbage: it has all the features and expect you to build your own workflows with it. You don't merely use Jira, you have to program your own interfaces in Jira to get your workflow into the system. And if you have the misfortune to have a project manager who thinks they're more technical than they are, they'll endlessly spin up new views, new workflows, and rearrange how the work is tracked in lieu of actually working.

I've been on that team.

One of Jira's features is the ability to describe the ticket workflow: the state machine that describes your process from the initial entry of the ticket all the way down to released software or project completion. This includes routing, so that as one team member does their part of the work, it automatically goes to someone else to do the next portion of the work.

Which brings us to Klinsten. They were working on a new team, and wanted to change the ticket status from its current status to whatever came next in the workflow. So they looked at the workflow.

A Jira ticket workflow. There are a pile of states arranged in a column, and connected by arrows. So many arrows. It's impossible to tell which arrow connects which two states. A second version of the diagram is in the picture, with transition labels attached. It makes less sense. For bonus points, the labels are a mix of English and Dutch

These are two different versions of the same workflow, one with transition labels added, which as you can see, does nothing to clarify the workflow. That it's a mix of Dutch and English doesn't help matters.

The purpose of this workflow is to help the team understand how to sequence and organize their work. But this workflow has so many states and so many transitions, it fails at this goal. Looking at it makes me just want to gesloten my browser tab, because this user isn't accepting any of this.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

Error'd: Zero to Zero in 0 seconds

14 August 2026 at 06:30

"So many zeroes! I'm in." W00H000! Kivi S. "found this ad in the wild. This must be a very large jackpot, look at all those zeroes!"

9bc39202c0254a468b610e65a50c4ab6

"I knew it!" groused an anonymous cynic. "Yes, SignUpGenius. We all know that SUCCESS is just an illusion."

e3080893822a416599d25e943913afd5

Another anonymous grouch reported "I guess JustWatch has suddenly become a bit precious about their sources"

458baffe588e4aa3ab4f4b9776fc0ef5

"These boots are made for crashing" thundered Michael R. "PII of the developer have been removed to protect the not so innocent." You can't hide PHP so easily.

7958aead3beb44579b149b2c75891bef

And again from prolific Michael R. "El Reg has been around for 30+ years and their code should be mature. I wonder about their SQL which seems to randomly return duplicate records. https://www.theregister.com/week". I'll be happy when Errord shows up on El Reg. Ok, no I won't but I'll at least be grouchy differently.

9489e6b6f15c4691828357c28262a628

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

CodeSOD: Never Eating the Cookie

13 August 2026 at 06:30

Maciej works as a freelancer, and that frequently means picking up old PHP code that nobody wants to support.

One project had been lingering for ages with key features missing. Specifically, it was supposed to make HTTP requests to other services on an interval, and use that to populate its data. "The old dev tried, but never got it working." It was Maciej's turn to give it a shot.

$ch = curl_init( $url );
curl_setopt( $ch, CURLOPT_COOKIEFILE, $cookie );
curl_setopt( $ch, CURLOPT_COOKIEJAR, $cookie );
curl_setopt( $ch, CURLOPT_COOKIE, $cookie);
// ... many other options set, of course not in a function, just copy-pasted in many locations in the code ...
curl_setopt( $ch, CURLOPT_TIMEOUT, $interval  );
$s = curl_exec( $ch );
curl_close( $ch );

This particular block of code appeared multiple times in the code. Every place they meant to send an HTTP request, they copy/pasted this code in. The URL would be a different value, but the bulk of the code was just a dozen lines of copy/pasted curl_setopts.

Now, I don't know that they were dreaming that setting CURLOPT_TIMEOUT was setting a recurrence interval. But they do call the value $interval, and I can imagine the ignorant hoping to set up cURL to automatically reinvoke the request on an interval. But even if that's their goal, that's not the actual problem with this code.

They initialize a cURL wrapper, set a pile of options, and then execute the request, storing the result in $s. And do you know what they do with the contents of $s after this?

Nothing.

The request works, perhaps not on an interval, and populates the variable, and they just never use it. The old dev "tried" and never got it working? It seems like they started and got bored.

There was far worse spaghetti code to manage in the project, but it was this gap that really got Maciej's attention.

[Advertisement] Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready.Learn more.

Branching Paths

12 August 2026 at 06:30

"You submitted a pull request."

Indika was, in fact, reviewing the comments she'd gotten on that very same pull request, when her boss, Bill, walked up behind her. What she didn't understand is why Bill said it like it was an accusation.

"Yes?" she replied.

"Okay, well, we don't do that here. You're new, so I'll let it slide, but please review the developer guide."

Well, Indika had reviewed the developer guide, or at least thought she had. As it turned out, there was the official, company wide developer guide. That's the one she'd read. But Bill maintained his own, for his team. He hadn't ever told her about it, but apparently assumed she'd have the oracular blessings of Apollo and find it by herself.

It had this to say:

Branching is prohibited. Merging is a time wasting activity and goes against CI principles. Only use git to commit, push, and pull.

And rebase, presumably, if everyone was just committing on the main branch?

Indika asked one of her co-workers, Elise, over coffee: "Is this real?"

"Yeah," Elise said. "I'm not sure how he found out about your PR, I don't think anybody added him to the review. I mean, why would they?"

"Oh, I sent him the link," Indika said. "Just a whole, 'I'm new here, look at me doing the work!' type heads up."

"Oh yeah, definitely don't do that."

"So we do use PRs?"

Elise nodded. "Of course we do. We're not crazy. We just make sure Bill never finds out."

That seemed like a terrible way to work, but Indika went along with it, at least for a few weeks. Then an opportunity presented itself; she and Bill bumped into each other in the kitchenette grabbing coffee, and nobody else was around. At this point, Indika had already submitted a number of PRs without Bill knowing.

"Bill, I've been meaning to ask, what's your rationale for prohibiting branching?"

Bill loved being asked that question. "Well, well, it comes from twenty years of experience. What exactly does a branch get you?"

"A distinct history of changes that can be maintained and eventually merged in once a large unit of work has been done without disrupting other work that might be in flight?"

"Another point of conflict! A chance for the code you're working on to get stale. A chance to fall behind the rest of the team. Now, for a large open source team, with a lot of collaborators, a branch might make sense. I'm skeptical, but I can at least understand it. But for our internal team? It's just developers seeing a new toy and going, 'oh, shiny!'"

Indika sipped her coffee and went back to her desk. She was fortunate to have a window nearby, and looked at the squirrels playing in the branches of the tree.

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

CodeSOD: Public Private Partnership

11 August 2026 at 06:30

Eric O was trawling through an API for handling concurrency, and found this little mismatch between the comment and the definition:

/// <summary>
/// private Status, because while this object needs to be able to set the status, consumers should only be able to check it, lest everything break.
/// </summary>
public StatusType Status {
    get {
        return _status;
    }
    set {
        if (value != _status) {
            RaisePropertyChanged("Status");
        }
    }
}

It's very important we make this property private, lest clients abuse it, and unleash dragons, chaos, and other potential horrors. Given that this happens inside of a concurrency API, I can only imagine what could go wrong when you mess this up. So sure, the comment makes sense.

The definition on the other hand, doesn't agree.

In practice, it's probably fine to do it this way, and at least the comment will show up in the documentation. If a consumer of the API misbehaves, they'll at least see that the docs suggest this is private.

The joke, of course, is the idea that the users of the API are going to read the docs, or care that one of the public methods suggests that it should be private.

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

The Crossroads

10 August 2026 at 06:30
I moved some things around on my calendar. 4:00 PM today is open. Please come to the executive floor.

-Leila

For a while I was stunned, staring at the email in front of me. I’d just told my boss I was quitting, refusing a promotion into my recently-deceased mentor Aggie’s shoes. Now, the new head of Human Resources wanted to see me.

Me? A Tech Support drone with one foot out the door? Well, she didn’t know that yet, did she?

Something in me feared where this might lead. But, Leila had stuck her neck out to rescue me from CEO Gibbs. She seemed like she cared about making things better. I decided to hear her out. Figured I owed her that much before I blew outta there for good in two weeks.

I had way too many hours to kill. Between whittling down my overstuffed inbox and resuming casework, it should’ve been easy to distract myself, but I couldn't focus on a single thing. I’d just done what had once seemed impossible. My brain wasn’t letting go of that any time soon.

Megan and Reynaldo also handed in their resignations. I got their messages confirming as much. We met up for lunch at a nearby restaurant and celebrated, but I was distracted. Amid the smiles and positive energy, the meeting with Leila was all I could think about. Should I mention it? I decided not to, not until I knew more.

I dreaded the afternoon slog now more than ever, but somehow, it slogged. When the clock’s hands finally crawled to 3:30, I threw on my coat and hat and darted out for one last smoke break. Then, it was time for C-Town.

Consumed with nervous energy, I shunned the elevator to race up the stairs floor by floor. Figured I’d burn off some stress, which could only help with whatever came next. Also figured I’d have a minute to recover in the vast executive lobby before finding my way to her office. Instead, I found Leila standing right there, every bit as polished as our surroundings. She faced me with surprise. “Hello.”

I tried to speak, laugh, something. Instead, I doubled over, coughing and gasping for air that felt all too thin up in nosebleed territory. While recovering, I couldn’t help but notice the gleaming tile beneath my feet, contrasting against my work shoes encrusted with sidewalk salt. Such details seldom crossed my mind, but the sort of people who worked up there lived and died for such details. Face flush, I cleared my throat one more time and righted myself, looking her way. “’Scuse me.”

“That was a long way up,” Leila remarked, gesturing behind herself. “I thought we could visit the observation deck. Would you like something to eat or drink first?”

Truth be told, I was already dying for another smoke. “No, that’s all right.”

“Follow me.”

She led the way through massive, quiet corridors to a small room with glass walls and ceiling. At this height, all one could see outside was a thick lead wall of fog. Leila strayed up to the far wall, a jewel against the void, and glanced back over her shoulder with chagrin. “I’m sorry, the view’s not very good today.”

“I dunno, I kinda like it.” Something about foggy weather had always intrigued me. With the normal world gone, it seemed like anything could happen.

She beckoned me closer with one hand. I strayed up next to her right side, staring out at the shrouded view.

“I understand you and Agatha Shaw were close,” Leila began quietly. “You have my deepest condolences.”

A two-ton anvil crashed onto my nerves. My fists clenched up at my sides. I worked so hard to hold back the flash-flood of grief that I couldn’t string words together. I only trusted myself to nod.

She glanced my way, hesitating. “Would it be better if we rescheduled?”

“I’m here,” I forced out. “Whatever you have to say, say it.”

She nodded. “First: while you were out of the office, I asked Francis Bronson to hand in his resignation.”

I drew a blank on the name, and blinked her way in confusion.

“The manager who nearly destroyed a printer with his hair dryer,” Leila explained, “after I’d just made a company-wide push for everyone to respect our office equipment.”

“Oh. Hothead!” My nickname for the guy. I’d worked that case a few weeks ago, but it felt more like years. Hothead worked in HR—at least, he had. After disarming him, I’d sent Leila an email, appealing for help against someone who clearly shouldn’t have been managing a supply cabinet, much less human beings. Well, she’d delivered. A warm note of satisfaction offered me a welcome lift out of grief. “Thanks. Really.”

Leila smiled. “We make a good team, I think. Which brings me to the other thing I wanted to discuss. Something new.”

My gaze fastened onto hers with a mix of intrigue and dread.

“You and I both know how badly this place needs to change. Let’s work together and actually fix things. I want to create a Change Management team and make you Team Lead.”

I was speechless, caught completely off-guard.

“The first thing we’d do is attack our company-wide leadership problem. Audits, surveys, hearings … eventually, a reorg. Along with simplifying the corporate structure, we’ll get rid of all the—Hothead, you called him? All the other Hotheads.”

“The biggest hothead is sitting at the top of the whole rotten pyramid,” I blurted. “He ain’t budging. He ain’t signing off on this, either!”

Leila was unfazed. “I think we could frame everything in a way that makes Mr. Gibbs like it. After all, we’re reducing payroll. We’re better positioning ourselves in a tough economic time. Worst-case, we could always use the three magic words: Google Did It.” She smirked.

I couldn’t help smirking back. Then I remembered what I’d done just a few hours earlier. “But I’m outta here. I quit! Put in my notice this morning!”

Leila nodded calmly. “Do you have a new position lined up somewhere else?”

“No. I'm going freelance with friends.”

“Friends who are leaving the company along with you?”

I nodded.

She paused for thought. “If you were to lead my Change Management team instead, you’d be able to recruit internally for your team. Whoever you think would be the most helpful. You’d set the agenda for whatever’s most important to address so that other good people don’t feel like they have to get away from here. All of this would mean a promotion to Director, with a salary and benefits to go with. And if you need more bereavement time, I could arrange an indefinite leave of absence until you feel ready to come back.”

The breath died in my throat. Had I suffered a stroke? I must’ve had a stroke. No, she really said it. She was on the level. I could change things. I could hire my friends to help me do it. A raise, full bennies, working with her every day?

Only a fool would refuse. And yet, my gut ached at the idea.

I stood there, frozen and mute, until I remembered something in my trench coat pocket: RD, the rubber duck I’d miraculously rescued from Aggie’s former office. I reached into my pocket and seized him in my fist.

RD? Aggie? I thought. Whoever’s listening. It all sounds amazing. I know she means it. But … it’s another trap, isn’t it? Staying in this joint for any reason means betraying myself. Betraying everything.

“Listen,” I finally said, “I’m flattered you even offered, but it ain’t right for me. Don’t give up on your idea! I’ve got friends here with ideas of their own for changing things. 32-hour work weeks. The end of free overtime. A union. I’ll send ’em your way. Offer them a spot on your team.”

Leila sighed. “A union would be an especially tough sell to Mr. Gibbs, but that really is a case where Google Did It. We might scare him so much with that idea that everything else would seem harmless in comparison.” She faced me with a sad smile. “A shame that we’re losing you. I was starting to learn some interesting things about printers.”

I’d miss her, too. And yet, I was feeling surprisingly great about my refusal.

“Make sure you file for unemployment,” she said. “We won’t stand in your way.”

I offered my hand. “Thanks for everything, Leila. Whenever it is they kick you outta here for good, come find me.”

She shook, sad smile persisting. “Maybe I will.”


I told Megan and Reynaldo about the new Change Management team. Made sure they knew the offer was on the table in case they preferred that over jumping ship. Both were quick to say no. Like me, they were too excited about our plans to stop now.

For the next couple of weeks, there was still plenty of work to be done: transferring my open tickets to other support reps and all that. But there was barely any time for it. Coworker after coworker stopped by my cube to express their surprise and wish me well in whatever came next.

“You’ll never be problem-free,” one of them advised me. “Go looking for the problems you want to have.”

I felt happier, freer, more determined than I had in ages. It was the conviction of knowing I’d stuck to my guns to do the right thing for myself.

Sanjay also jumped ship to join us. And there was one last surprise that came in the form of a phone call. The name on my work phone’s caller ID was DRACORA, P. So-called “Dracula!” Having a fairer opinion of her than most, I picked up without any sense of dread.

So-called “Dracula!” She hadn’t been so bad at all. Surprised, I picked up in a hurry.

“I’m so sad to hear you’re leaving!” she said. “What kind of freelance work are you doing?”

“All sorts of IT projects,” I replied. “Maybe some consulting on the side.”

“I have a friend who needs help setting up a website for her business. Is that something you could help her with?”

My eyes flew wide open in shock. “Sure!”

“I’ll put you in touch with one another.”

“That’d be swell. Thanks!”

We exchanged contact info. She promised to keep pointing friends our way whenever she could.


On my last day, I sent my personal contact information to my coworkers. I reminded them of Leila’s offer and urged them to keep me posted on their different causes. Then my friends and I walked out of a building that no longer had a hold over any of us.

It felt pretty damn swell.

Our accountant helped us incorporate RD IT Solutions. Only close friends knew it stood for “rubber duck.” The company covered medical and other relevant expenses for everyone. There was no hierarchy. Everyone had equal financial stakes and an equal say in company decisions.

After decades of being an expert at what I did, I was back at square one, learning the ropes. So much of what we had to learn for our business could only be learned through failure. Still, it felt rewarding to challenge my brain in new ways. The new gig let me wear lots of hats, from tech support to coding to business admin.

With no more regular paychecks, we had to tighten down our finances to what was critically important. We worked remotely at whatever times worked best for us, with the occasional meet-up at a public place or someone’s home. We all knew that any one of us having some kind of trouble could count on the rest of the group to help out as best as they could.

Megan quit smoking again. I cut way back myself. Wasn’t trying to, I just haven’t felt the need as much. Also stopped having those nightmares. It no longer feels like I’m living just for time off and weekends. I don’t spend my Sunday nights dreading Monday.


Dracula really did introduce us to our first client. It’s crazy what the universe puts out there when you go looking for it.

We met up remotely for our first client meeting to discuss requirements and expectations. When the topic of deliverables came up, our client scrunched her nose and interrupted Megan mid-sentence. “I don’t trust email! I’d rather you fax me the files.”

We were building a website for her.

“You mean, the source files?” Megan soldiered on bravely.

“Just fax me the codes,” the client said. “My nephew can re-type them into the Internet.”

She provided a fax number, fully expecting us to print out several hundred lines of code to send over. After deploying our first stab at a website that met her requirements, we did just that, mostly out of curiosity.

A few days later, she called us in a huff, saying her website looked like random letters. Her nephew had typed the code into Facebook.


FIN

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

Error'd: Time Wounds All Heels

7 August 2026 at 06:30

Looked to the past for some time-traveling entries to round out a themed post. They're 25% fresh.

Robert apparently got a notification of a planned past delivery. It could be just a case of two systems that don't report different time zones, but even so, that's a wtf. Says he: "I just received an email from OnePlus this morning letting me know they have updated the planned delivery date for my order to Yesterday. I guess the delivery driver is going to time travel to get there on time since it still hasn't arrived. "

10c678e3b0bc4ec79761a9998cc687bb

Kinkster Ypsilon Omega was really turned on by a time-traveling hottie who posted a photo an hour before joining. "(Heavily redacted screenshot.) Either Fetlife (a kink community website) is very welcoming to time travelers, or allows new members to upload their profile picture. Because time handling code surely never fails..."

17a1e640495b493a9e34c463749ade73

ERIC P. shared a photo from 2023. "My 2008 Ford has suddenly time-warped 1024 weeks into the past. GPS date roll-over bug. No updated firmware available. No way to set the calendar manually. No way to turn off the date display."

a8a33a11c9624ab5af5e7272a838237e

Marc Würth "... just added a new RSS feed to my Netvibes dashboard (great tool otherwise, by the way). While it was still fetching the feed, it showed these peculiar crawling stats. Once fully loaded, it showed sane info, though." I'm curious about the specificity of 261 years.

d2c88222c5554be2bcd6c051f19072ae

Quite recently, an anonymous slightly flexed "Not only did I receive two parcels prior to the Roman conquest of Britain, but the date calculation for the combined notification has then failed and returned the Unix epoch." For those who missed the flex, dig Wikipedia: "Wardian London is considered to be the most expensive residential development in East London." I guess they can afford professional time travelers.

fac2c471b782468c8c2e3a1afdb808f4

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

A More Civilized Age

6 August 2026 at 06:30

Greta (previously) sends us more updates from her "Ancient Development Environment".

An important task an IDE must do is report build errors to its users. Arguably, that's one of the most important parts. I wouldn't know, I insist on building from the CLI all the time, because IDEs confuse and frighten me. I recognize I'm the weird one here, who is more comfortable in GDB than in a GUI debugger, but this isn't about me, it's about the IDE Greta is using.

It needs to display an error. Why does it need to display an error? Well, Greta hasn't figured that out yet. The error I'm about to show you doesn't really explain what happened or why or give any hint as to what needs to be done to fix it. To make matters more confusing, it doesn't happen consistently, so simply re-running the build could potentially fix it.

None of that is why we're here, though. What makes this a WTF is how the error is displayed:

An error box in a very Win3.1 style. It displays the error as a tree view, and the error is actually an XHTML document. But it's not rendering as XHTML, it's just raw HTML code, shown as a tree, like a really cruddy DOM inspector

Greta shares her bullet points about what she hates about this:

  • It's a popup that happens arbitrarily during build, under unknown conditions
  • It says nothing about what went wrong, or indeed if anything went wrong
  • It's in a non-user-legible XHTML format. It shows the markup of this XHTML rather than it being rendered.
  • It arguably shows the XHTML markup in the worst way possible: in a tree view (?!) with one row per source line
  • The markup isn't even well-formed. I'll let you count the reasons why.
  • The control used is from some sort of legacy windowing toolkit that doesn't support text anti-aliasing.
  • The first button on the upper-left, "Get latest C++ Builder Direct headlines from the Internet", does nothing.

I'd honestly forgotten about the era when the Internet was still kinda novel so every application had a "push a button and go to our web page, and this somehow definitely won't just break when we change our URL structure in the future."

Greta adds:

For all of the hatred I harbour for this, the second button ("Information about C++ Builder Direct") brings up the following powerful dose of 90s nostalgia, so I can't stay mad:

An about box, with a very 16-color gif globe, wrapped in a cable terminated with an RJ45 connector, along with some copy about how C++ Builder will reach out to the Internet and bring the latest news TO YOU. Aren't you excited?
[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

CodeSOD: Connection State

5 August 2026 at 06:30

Frederick A sends us a bit of null checking code, and offers us a better solution.

class ConferenceService
{
	/// <summary>
	/// Checks if conference is active
	/// </summary>
	public bool IsCalling()
	{
		try
		{
			return m_ConnectionService.Core.State.IsWebRTCConnected;
		}
		catch
		{
			return false;
		}
	}
}

This is for a web conferencing tool, which uses WebRTC to set up connections between clients in the chat. This function checks if the chat is active by checking a IsWebRTCConnected flag. But as you can see in this code, that flag is on a long chain of objects, some of which may not exist when this function is called. Thus, we wrap the whole thing up in a try/catch. If anything throws an exception, we know we can just return false. It's probably fine.

The obvious and easy fix, which Frederick proposes, is to use the C# coalescing operator: ?. m_ConnectionService?.Core?.State?.IsWebRTCConnected ?? false would solve this problem just fine.

That said, I wouldn't say that's a true fix. We're talking about a state machine here, though admittedly with two states under discussion (connected/disconnected), though there are probably more not being checked here. This information should be managed via a state machine, not via boolean flags stuffed deep in an object chain. The fix isn't a WTF, but it definitely hints at a better way to manage all of this. Now, my solution likely requires a lot more modification and code changes than what we have here, so I'm not suggesting anyone go off and rewrite this from scratch just to have a cleaner way of managing state. But folks definitely should think more carefully about how they manage state.

[Advertisement] Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready.Learn more.

CodeSOD: Always Take the Option

4 August 2026 at 06:30

Frequent submitter Capybara James sends us this simple snippet, which highlights that even when you have the lovely convenience of Optional types, you can use them wrong.

if (StringUtils.hasLength(dto.getAssetModelUUID())
		// Other conditions
		) {
	return Optional.ofNullable(dto);
}

We access the getAssetModelUUID member of dto, and if it's a non-empty string, we can then return a nullable of this thing that's definitely not null in the first place.

Okay, in the scheme of things, that's not that bad. All we're really doing is just not using the syntactic sugar that automatically boxes your dto into a nullable type. On it's own, it's not bad, just ugly. But like all things, it doesn't exist on its own. It exists inside of a giant pile of code where this pattern is used all the time. Even functions which don't return nullable types box (and unbox) the type. Optional is scattered through the code like a magic ward against null reference exceptions.

Does it help? No, not really, the code is buggy and error prone. Will it ever get fixed? Probably not in this lifetime.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

Lose Some Padding

3 August 2026 at 06:30

Flat-file style databases were designed to fit the constraints of the systems they were running on. You specify your schema in terms of "how many characters in a file we use to store this data", meaning something like this: JOHN    SMITH    12343rd    StAnytown PA12345 is read in my knowing that the first name field is 8 characters wide, the last name field is 8 characters wide, the street number is 4 digits, and so on.

It's also a terrible schema, and woe to anyone with a long name. But many a mainframe had a similar schema.

Now, let's think about maintenance here. What happens when we also want to store a middle initial? We've created for ourselves a problem. Somehow, I have to insert a character into every row, which basically means making a new table with a new schema, copying every record out of it and updating it to use the new schema. I can't just ALTER TABLE like an RDBMS. And worse, every piece of software that touches the table also needs to be updated. On a large legacy system, a simple task like "add a field to our database" could take weeks of developer time, and depending on the software, be a high risk operation.

Which is why the smart developer, when working with flat files, includes padding. Maybe my schema for an address record looks more like this: JOHN    SMITH    12343rd     StAnytown PA12345                . That's 16 characters of padding at the end of the file. Now somebody says that I need to store a middle initial, I can just shrink the padding by one and add a middle initial field, like so: JOHN    SMITH    12343rd     StAnytown PA12345Q               

Is this elegant? No. But it works. I haven't changed the length of the row at all, so I don't need to move data around. Software modules only need to be updated if they care about what's in the middle initial field; if they're out of date, they just think there's a "Q" in the padding, and don't care.

In real-world applications, instead of putting all the padding at the end, you'd usually put the padding in a few spots in the middle of the table. Any time you need a new column, you just steal a few characters from padding. Sure, someday you'll run out of padding, or at least out of padding blocks big enough for your new field, and then you'll have to do the hard work of shuffling data around. But in practice, you can get very far without that happening.

Which brings us to Brenda's adventure. Her team supports an IBM mainframe storing data in VSAM flat files. In other words, they've been doing the sort of thing I just talked about for many, many years.

Of course, in the modern era, you can't just leave your data sitting in an mainframe. Even if the mainframe is the source of truth, you want to be able to report on it and connect it with your other data systems. You need to, somehow, get the data into a modern RDBMS.

So the company hired a bunch of developers to write an extract-transform-load process, which pulls the data out of the mainframe. The mainframe team handed them a "copybook" for the flat file, which described the structure, and the ETL devs went to work.

And maybe those ETL devs didn't understand the importance of padding. Maybe they just missed the padding. Whatever it was, there were several places where the data was structured like SOME_USEFUL_FIELD PADDING PADDING PADDING SOME_OTHER_FIELD, and they opted to split it like so: SOME_USEFUL_FIELD PADDING PAD, DING PADDING SOME_OTHER_FIELD.

When they released this process, it was fine. The padding characters got stripped before displaying, so the users never saw them. They were stored in the database, though, so when someone tried to reconstruct the data in a way that was compatible with the flat files, you could just concatenate the columns together and get a valid result.

It was fine- until it wasn't. The ETL devs, bless their hearts, only tested against the production mainframe. And why not, they were doing read only operations, what's the harm? Had they tested against the development mainframe, they would have seen new features in flight, features which consumed some of that padding, and realized that they should have paid closer attention to the copybook.

But instead, the test cases all passed. The software was, as far as the project managers and ETL developers could tell, working perfectly. So it was accepted, released to production, and running for a few weeks before the mainframe released its features. Those features then ruined all the beautiful reports with extraneous data.

And since the ETL devs were on contract, any request to have them rework it under the original contract was met with a stern "Works as designed". Instead of paying the contractors to come back and rework the system, the mainframe devs instead were tasked with finding different padding fields they could use, padding fields which wouldn't end up ruining any reports management liked to see.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

Error'd: The Song that Never Ends?

24 July 2026 at 06:30

"Watch to the end", the scammers demand. In today's episode of Error'd, the end is a long time coming. But at least it's amusing. In the meantime, Amazon irked and/or terrified thousands of their customers last week by mailing out ridiculously inflated bills. It's been covered extensively elsewhere but why should we miss all the fun?

It was Willy who worried "Amazon prices seem to have crept up this month, finance are going to have something to say"

a7e89b1dc59e4c3ba3b8571a056825d6

Dave A. is on the horns of a dilemma. "Is it optional or required? Make up your mind, TAP! I'm TAPping my fingers waiting for you to decide."

ffd147510c4b455c9c43e60763e68bf8

"This rating is through the roof!" exclaims a regular who wants to be Anonymous today. "We're done with linear rating scales. Now you can have 5 stars on both X and Y axes. Also, given this is for a company that cleans roofs, we can make a joke this rating is out of the roof, and out of the <div> as well. In case you're interested and in case you want take a screenshot yourself: https://mijndakschoon.nl/" It's real; I checked.

11083f7140b449a48c5218843699cc24

Richard H. found a flubstituted email. "Quickbooks here reminding me that I failed to pay invoice number "{{rand_invoice}" Or this might be a scam/phishing email. Hard to tell." I'll bet {{money}} on scam.

710be181fec049ab8f717ae185783f5f

B.J. H. is usually quite concise. Usually. "If this is ALL NORMAL I'm worried what will happen in an emergency"

7885ce2a1ae64033b9b46e472925edd9

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

Classic WTF: My Many Girlfriends

23 July 2026 at 06:30
Honestly, with the wildfire smoke and the oppressive heat, maybe it's time to find a nice quiet cave to hang out in. Something with no natural light and no natural ventilation. I wonder if anybody has a place like that… Original. --Remy

In the long ago, wild-west days of the late 90s, there was an expectation that managers would put up with a certain degree of eccentricity from their software developers. The IT and software boom was still new, people didn't quite know what worked and what didn't, the "nerds had conquered the Earth" and managers just had to roll with this reality. So when Barry D gave the okay to hire Sten, who came with glowing recommendations from his previous employers, Barry and his team were ready to deal with eccentricities.

Of course, on the first day, building services came to Barry with some concerns about Sten's requests for his workspace. No natural light. No ventilation ducts that couldn't be closed. And then the co-workers who had interacted with Sten expressed their concerns.

During the hiring process, Sten had come off as a bit odd, but this seemed unusual. So Barry descended the stairs into the basement, to find Sten's office, hidden between a janitorial closet and the breaker box for the building. Barry knocked on the door.

"Sten awaits you. Enter."

Barry entered, and found Sten precariously perched on an office chair, removing several of the fluorescent bulbs from the ceiling fixture. The already dark space was downright cave-like with Sten's twilight lighting arrangement. "He welcomes you," Sten said.

"Uh, yeah, hi. I'm Barry, I'm working on the Netware 3.x portion of the product, and Carl just wanted me to check in. Everything okay?

"This is acceptable to Sten," Sten said, gesturing at the dim office as he descended from the chair. Sten's watched beeped on the hour, and Sten carefully placed the fluorescent bulb off to the side, in a stack of similarly removed bulbs, and then went to his desk. In rapid succession, he popped open a few pill containers- 5000mg of vitamin C, a handful of herbal and homeopathic pills- and gulped them down. He then washed the pills down with a tea that smelled like a mixture of kombucha and a dead raccoon buried in a dumpster.

"He is pleased to meet you," Sten said, with a friendly nod. Barry blinked, trying to track the conversation. "And he is pleased with it, and has made great progress on building it. You will like his things, yes?"

"Uh… yes?"

"He is pleased, and I hope you can go to him and tell him that he is pleased with this, and set his mind at ease about Sten."

So it went with Sten. He strictly referred to himself in the third person. He frequently spoke in sentences with nothing but pronouns, and frequently reused the same pronoun to refer to different people. The vagueness was confounding, but Sten's skill was in Netware 2.x- a rare and difficult set of skills to find. So long as the code was clear, everything would be fine.

Everything was not fine. While Sten's code didn't have the empty vagueness of unclear pronouns, it also didn't have the clarity of meaningful variable names. Every variable and every method name was given a female first name. "Each of these is named for one of Sten's girlfriends." Given the number of names required, it was improbable that these were real girlfriends, but Sten gave no hint about this being fiction.

There was some consistency about the names. Instead of i, j, and k loop variables, you had Ingrid, Jane, and Katy. Zaria seemed to be only used as a parameter to methods. Karla seemed to be a temporary variable to hold intermediate results. None of these conventions were documented, obviously, and getting Sten to explain them was an exercise in confusion.

It led to some entertaining code reviews. "Michelle here talks to Nancy about Francine, and then Ingrid goes through Francine's purse to find Stacy." This described a method (Michelle) which called another method (Nancy), passing an array (Francine). Nancy iterates across the array (using Ingrid), to find a specific entry in the array (Stacy).

Sten lasted a few weeks at the job. It wasn't a very successful period of time for anyone. Peculiarities aside, the final straw wasn't the odd personal habits or the strange coding conventions- Sten just couldn't produce working code quickly enough to keep up with the rest of the team. Sten had to be let go.

A few weeks later, Barry got a call from a hiring manager at Initrode. Sten had applied, and they were checking the reference. "Yes, Sten worked here," Barry confirmed. After a moment's thought, he added, "I suggest that you bring him in for a second interview, and have him walk you through some code that he's written."

A few weeks after that, Barry got a gift basket from the manager at Initrode.

Thanks for the tip

Sten did not get hired at Initrode.

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

Classic WTF: Server Room Fans and More Fun

22 July 2026 at 06:30
It's been pretty hot lately. Probably should use a fan to cool off. Mind the trip hazards. Original. --Remy

"It's that time of year again," Robert Rossegger wrote, "you know, when the underpowered air conditioner just can't cope with the non-winter weather? Fortunately, we have a solution for that... and all we need to do is just keep an extra eye on people walking near the (completely ajar) server room door."

 

"For as long as anyone can remember," Mike E wrote, "the fax machine in one particular office was a bit spotty whenever it was wet out. After having the telco test the lines from the DMARC to the office, I replaced the hardware, looked for water leaks all along the run, and found precisely nothing. The telco disavowed all responsibility, so the best solution I could offer was to tell the users affected by this to look out the window and, if raining, go to another fax machine."

"One day, we had the telco out adding a T1 and they had the cap off of the vault where our cables come in to the building. Being curious by nature, I wandered over when nobody was around and wound up taking this picture. After emailing same to the district manager of the telco, suddenly we had the truck out for an extra day (accompanied by one very sullen technician) and the fax machine worked perfectly from then on."

 

"I found this when I came back in to work after some time off," writes Sam Nicholson, "that drive is actually earmarked for 'off-site backup'. Also, this is what passes for a server rack at this particular software company. Yes, it's made of wood."

 

"Some people use 'proper electrical wiring'," writes Mike, "others use 'extension cords'. We, on the other hand, apparently do this."

 

"I was staying at a hotel in Manhattan and somehow took a wrong turn and wound up in the stairwell," wrote Dan, "not only is all their equipment in a public place (without even a door), it's mostly hanging from cables in several places."

 

"I spotted this in China," writes Matt, "This poor switch was bolted to a column in the middle of some metal shop about 4m above ground. There were many more curious things, but I decided to keep a low profile and stop taking pictures."

 

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

CodeSOD: Classic WTF: Fork and Log

21 July 2026 at 06:30
We keep our summer break going. Today, there's something floating in the pool, and I don't think it's a Snickers bar. Original. --Remy

A few years back, Adam C. was brought in to help with some performance problems that appeared while load testing a VXML Platform. The project was already well behind and they couldn't figure out why the system kept falling over under a very slight load. To make matters worse, Adam had absolutely no prior knowledge of the system or its software other than Wikipedia’s definition of what VXML is.

A veteran to these sorts of situations, Adam grabbed a coffee, a donut, and then started picking through the application logs to get a feel for what the system is doing and where something might be going wrong.

Looking in /var/log/messages, he was pleased to find with several days’ worth of messages, but over and over again, the same entry popped up:

Exception encountered writing error log. 

"Seriously, who logs an error that they can't log an error? ...and is that even possible?" Adam wondered aloud after seeing the same senseless message for what he figured to be the hundredth time.

Frustrated, and hoping to learn what ludicrous conditions might precipitate a log to contain such a message, Adam dug into the source and hit paydirt in the form of this wonderful nugget of code:

public void error(String logID, String errStr) {
  StringBuffer errLogCmd = new StringBuffer("/usr/bin/logger -p ");
  try {
    Runtime rt = Runtime.getRuntime();
    errLogCmd.append(errlogFacility);
    errLogCmd.append(" -t ");
    errLogCmd.append(logID);
    errLogCmd.append(" ");
    errLogCmd.append(errStr);
    rt.exec(errLogCmd.toString());
  } catch (Exception ele) {
    System.out.println("Exception encountered writing error log." + ele.getMessage());
  }
}
As he mentally parsed his way through the code, Adam could feel his breakfast tickling up from the back of his throat in reaction to the number of WTF’s he found himself facing.

He couldn’t decide what about the implementation was worse - forking off an external process to log something, the fact that log4j could have been used to send stuff to syslog (which the project used elsewhere), or that since the program's output was already piped to /usr/bin/log, just doing a System.out.println() would have been equivalent to this code.

As it turned out, this wasn’t the root cause behind the performance problems, but needless to say, that code got junked rather quickly and he moved on to looking for the next performance bottleneck.

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

CodeSOD: Classic WTF: The Table Selector

20 July 2026 at 06:30
It's summer break time, which as always, means we dip back into classic articles. Today, we pick which table we want. Original. --Remy

"In my native language of German," writes Christian, "the word quellcode is a pretty direct translation of 'source code'."

"Unfortunately, bad code seems to cross language barriers - as does that famous three-letter explicit adjective. But occasionally I’ll find a piece of quellcode that deserves its own special, localized expletive: quäl-kot. When I stumbled across this interface in our quellcode, quäl-kot was the first thing that came to my mind."

public interface ITableSelector
{
    string selectTable1();

    string selectTable2();

    string selectTable3();

    string selectTable4a();

    string selectTable4b();

    string selectTable5();

    string selectTable6();

    string selectTable7a();

    string selectTable7b();

    string selectTable8();

    string selectTable9a();

    string selectTable9b();

    string selectTable10();

    string selectTable11();

    string selectTable12();

    string selectTable13();

    string selectTable14a();

    string selectTable14b();

    string selectTable14c();

    string selectTable14d();

    string selectTable15();

    string selectTable16();

    string selectTable17();

    string selectTable18();

    string selectTable19();

    string selectTable20();

    string selectTable21a();

    string selectTable21b();

    string selectTable22();

    string selectTable23();

    string selectTable24();

    string selectTable25();

    string selectTable26();

    string selectTable27();

    string selectTable28();

    string selectTable29();

    string selectTable30();

    string selectTable31();
}
[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

Error'd: Princess Pricing

17 July 2026 at 06:30

Sam suggests this Error'd indicates "Disney+ preparing the ground for usage-based billing." I'm intrigued by the idea that Disney might charge by the minute, but I suspect the reality is far more mundane.

13e08caf609f4133afd419a76acb432a

Silly prices at online shopping sites don't usually make it through the gauntlet here, but I'm making an exception for the math, as Rob H. points out "it ain't mathin'."

0924416949684290b36ad0d823129181

and Harrison suggests a novel kind of discounting math "I went to the supermarket later at night for some beers, and had a snoop around the yellow sticker items for anything I might need or could freeze. This bakery item was priced in reverse, Was: 0.00, Now 1.99, discounted by negative infinity percent, and infinity is even printed upside down somehow."

d41527fab58d46fa97f4477dfcbf659d

We've got a mojibake from dragoncoder047: "Was browsing through the widget options on my iPhone home screen and found that Game Center had decided to do this. Mind you, my iPhone was, and always had been, set to English."

c4fb48f719b34b5393e274cc6bc48f2a

Finally a combination of typical time travel and package tracker shenanigans, not explained by time zone hijinks. Evelyn notes "Apparently the package was registered in July, on its way during January, and then got back to July."

ef7d6784e50a48eebfaf4b912c8caa55

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

CodeSOD: Wait Longer

16 July 2026 at 06:30

Karen was maintaining some specification tests that were flaky. Not extremely flaky, but three or four times out of a thousand, the tests would just fail. The tests were complicated, and some of the operations were timing sensitive, so it wasn't precisely surprising- but the problem was that they were actually generous with their timing windows. The unit tests passed consistently, it was only these functional, specification-based tests that failed.

So, for example, there were sections in the tests where they wanted to wait at least 2ms. Since the code and tests were in TypeScript, they used the setTimeout function, which per standard JavaScript documentation warns that it may wait longer. But again, Karen was fine with longer.

Unfortunately for Karen, the documentation for NodeJS is less specific, as it makes no guarantees about when the timeout function gets invoked. This means that it can fire the timeout before the time has elapsed.

After many, many hours of debugging, that was exactly the situation that Karen found herself in. Which is why her very simple wait function went from:

export const wait = (ms:number) => new Promise((complete) => setTimeout(complete, ms));

To the much more awkward:

export const wait = (ms: number) {
    const target = performance.now() + ms;
    return new Promise((complete) => {
        const checkReady = () => {
            if (performance.now() > target) {
                complete();
            } else {
                setTimeout(checkReady, 1);
            }
        }
        setTimeout(checkReady, 1);
    });
}

This version of the function checks the time every millisecond, and only completes the operation if we've waited at least as long as our target duration. This ensures that the timeout never fires too soon and it fixes the janky tests. But it's also terrible. Terrible that it exists. Terrible that this is the best solution. Terrible that our functional tests need to be so time sensitive. And terrible that the Node runtime actually breaks the one consistent scheduling guarantee that pretty much every other scheduler does: that it'll wait at least as long as you asked, but might wait much longer.

At best, we can say, "at least it's only testing code."

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

CodeSOD: The Error Check

15 July 2026 at 06:30

Today's submission is less a WTF and more a, "Yeah, that'd annoy me too."

Stevie works in a code-base that's largely C, which means function return values are usually used to communicate to status codes. The standard:

BOOL success = someFunc();
if (!success) {// handle the error

If someFunc returns TRUE, we succeeded, otherwise we failed.

There's nothing wrong with that convention. But there is something wrong with one of the long-time developers on the project, because they have their own idiom for doing this. And they've been around long enough that their approach is the convention other developers follow. It's not wrong, per se, just confusing:

BOOL error = someFunc();
if (error == FALSE)
{
    //handle the error
    errorCode = GetLastError();
    //…
}

Yes, pretty much anywhere an error can happen, they check if error == FALSE, and if that's true, they have an error.

I'd say, "at least they're consistent", but they're not. It's the convention, sure, but nobody wrote this down as the convention. New developers come in all the time, and they start out writing code in a more "normal" pattern. But the existing code has its pattern, and there is a lot of it. It has a mass and an inertia that is stronger than any developer. They don't change the code, the code changes them.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

CodeSOD: AAYFN

14 July 2026 at 06:30

Jason M sends us some Ruby code.

def bv(prop, tv="Yes", fv="No", nv="Not Specified")
  v = self.send(prop)
  if v === true
    tv
  elsif v === false
    fv
  else
    nv
  end
 end

The obvious WTF here is the function name and parameter names. AAYFN(always abbreviate your function names) seems to be the convention here. But it also contains a more Ruby-specific WTF.

The function name bv is short for "boolean value", obviously. tv is the "true value", fv is the false value, and nv is the null value. So this is really about pretty-printing boolean values and converting them to strings. While the terrible names make it hard to understand, it's not that hard to figure out what's going wrong here. I hate it, don't get me wrong, but it just makes me sigh with disappointment, not groan.

No, the thing that makes me groan is v = self.send(prop). This is a very Ruby idiom that lets you access a member of the class by name; essentially it's like doing self.prop, but since prop is a variable containing a property name, we have to send it.

This is metaprogramming by strings, which is the main reason I end up hating it. But in this specific instance, it offers us a lot of potential issues. First, if prop is anything not boolean, we just return "Not Specified", which is incredibly misleading. I'd argue that if we attempt to use it on a non-boolean field we should throw an exception. Which opens the question: if prop doesn't exist, is that a non-boolean field, or is that a "enh, just call it null" situation? Because right now, that will throw an exception. It'll also throw an exception if the thing being accessed is a function that takes parameters. These behaviors may be surprising.

Now, I don't know the calling pattern. It's possible that whatever function calls this already has a good list of the allowed boolean values, and will never call this on a prop that doesn't exist. Certainly, that's what the Ruby docs recommend. But I'm going to hate it anyway, because this kind of runtime metaprogramming by passing strings around is eternally asking for trouble. And while I haven't done a huge amount of Ruby, I've done enough to know that any non-trivial codebase ends up like this once the metaprogramming band-aid is taken off.

Now, if you don't mind, I'll go back to doing my metaprogramming with C++ templates, which are simple, clear, and never result in wildly unmaintainable code because you ended up reimplementing LISP in template operations.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

The Easy No

13 July 2026 at 06:30

There were thousand tickets in the backlog, and I was on the trail of a weird printer issue. I had a suspect, but that wasn't enough to close the ticket. I'm Anonymous. This is my story.

Whenever you hit the Print button, a request launches into the ether. “Print one copy of this file, double-sided.”

But you can’t just chuck it out there aimlessly. You gotta tell it where to go. So that’s why we have Internet Protocol, or IP. Every computer, every printer, every device on every network has one or more IP addresses for different operations. They’re just like the address you scribble on the envelope holding Grandma’s Christmas card. Send your print request to the right IP, and you’re in business. Send it to the wrong IP, and they’ll be shaking their heads on the other end. “Print? But all I know how to do is tell you the weather!”

As for what happens next, you’re at the mercy of the program’s error handling. If you’re lucky, they’ll send a detailed error message. If you’re not, they could ignore you, act up in weird ways, or completely self-destruct in a violent crash.

I was neck-deep in a Tech Support ticket concerning a printer in Human Resources. Not only was Grandma’s Christmas card landing in Pluto, the Plutonians were writing back, in the form of mysterious complaints that were nothing like the documents the printer had been tasked with.

My troubleshooting hadn’t shot much trouble at all, so I’d bugged some friends for ideas. Reynaldo had been reminded of an old, retired HR program for logging anonymous employee complaints. While our developer friend Megan quit our icy outdoor break spot to dig up dirt from the software side of things, Reynaldo and I went to his cubicle to trace IPs. He suspected a conflict … and that’s just what his command-line requests revealed. We leaned over his laptop together, studying a couple of terminal windows and their cryptic output to his even-more-cryptic inputs.

“Whenever that printer got set up in HR, it received an IP that was already in use by this server—” he jabbed an index finger at the relevant part of the screen “—which is apparently still up and running, even though I swear it was decommissioned.” Reynaldo frowned. “Who wants to bet that stupid complaint program has some kind of ability to print cached complaints?”

I frowned, too. “We won’t know for sure unless Megan digs something up.”

Reynaldo flashed me a look of pure cynicism. It wasn’t an indictment of Megan’s skill, more skepticism toward the idea of anyone having properly documented this mess. We had better odds of hitting a billion-dollar jackpot.

I sighed. “It’s an easy workaround, at least: assign the printer a new IP. But we’ve got bigger problems here. Why’s this zombie server still up and kicking? Why’d an IP conflict ever occur in the first place?”

“Don’t get me started on our FUBAR IP allocation and tracking.” Reynaldo’s lowered voice contained plenty of venom. “We’re talking multiple pools to track, multiple spreadsheets that have to be manually edited. Problems that are easy enough to fix! Why spend money on honest-to-goodness network management software?”

Short-term budgeting preventing long-term improvements: a damned shame we’d both seen all too many times. Being a tech support drone, having an innate desire to help that no amount of baloney could stamp out, I once more found myself longing to fix the unfixable.

“In my ticket notes, I’ll make noise about a more permanent solution,” I said. “At the very least, some bigwig oughta leap at the chance to save 3 cents of electricity a year by putting the complaint-spewing server out of its misery.”

I’d already resolved to contact Leila, the new head of HR, about this ticket. She needed to hear about Hothead, the manager-type who, in a fit of frustration, had taken a hair dryer to the same printer and nearly trashed it. I could also tell her about these lingering network vulnerabilities that would continue causing problems for everyone down the line.

I decided to do it without telling Reynaldo. I had the feeling that if I let him in on it, his eyes would roll clear out of his skull.


Working from Reynaldo’s cube, I assigned the HR printer a new IP address and once again cleared its queue. Then I sent Tony, the ticket holder, a private message asking him to give printing another go. I felt pretty good about his chances, but I’d wait for him to give me the all-clear before closing the ticket.

With the messaging app still open on my phone, I saw that Megan had invited me to drop by her cube. From Networking Central, I hoofed it up to Developers’ Row.

Megan’s cube was a familiar spot filled with bright, cartoony posters and figurines, the only splashes of color for miles. The titles and characters drew blanks in my over-the-hill brain. I kept meaning to ask her what they were, why she liked them. Another time, maybe. I found her with her back toward me, leaning intently in her swivel-chair toward the single monitor over her laptop docking station.

“Now a good time?” I asked under my breath.

She whirled around, tense, then relaxed with a smile, her gaze livelier than I’d seen it in a while. She picked up a small external memory stick from her desk to proffer my way. “That’s the HR application’s source code!” she murmured in conspiratorial fashion. “Guess where it came from?”

I smirked. “I know the answer should be ‘a code repository,’ but in this joint, that’s asking too much.”

“You’re right!” she replied. “It was never in a repo, never in source control. It lived and died on one dev’s local machine, a dev who retired way before I got here. His successor hung onto all the old code from that guy’s machine, just in case. But yeah, we don’t officially support the application anymore.”

Megan turned around, using her keyboard to tab over to an open window in a code editor. From over her shoulder, I saw line after line of an archaic programming language that the Egyptians might’ve used to build the pyramids.

“This code’s an undocumented disaster,” she said.

“There was an IP conflict at that,” I told her. “The print requests were going to a server that’s still running this thing. We assigned the printer a new IP, but the mystery server remains a head-scratcher.”

“I’ll trace through and figure out what it does when it gets a print request. Improve its error-handling in general,” Megan promised, more excited than daunted by the prospect. “I mean, if it is still up and running, I could tweak, recompile, and redeploy it so it doesn’t—”

Insistent metallic knocks sounded behind us. “Excuse me!”

It was the sort of pointed voice that somehow ignored your ears and stabbed into your gut instead. Megan froze, tense. I turned to find a woman at the threshold with the bearing of a vindictive hall monitor, rapping a fist against the cube’s bare metal frame.

She leveled a withering frown at Megan. “I was hoping for a status update on the Hewville refresh! Did I hear you say you were planning to work on something else?” The question sounded more like a threat.

This had to be Megan’s boss. Megan remained tense from head to foot. “I—”

“I need you to focus on your assigned projects. The things you can actually bill your time against.” After delivering the condescending reminder, the boss’ glare shifted my way. “And you are?”

I shoved unease aside and put on the game face I’d spent decades perfecting. “Tech Support. I meant no harm, ma’am. I was just asking Megan for help with an open support ticket.”

“She doesn’t have time for that!” the boss scolded. “If you truly need help from this department, then you must escalate your ticket through the proper channels.”

I already knew how that went: pulling together screenshots, logs, and other detailed information, only to receive half of a sentence fragment a few days later, asking for something I’d sent with the first message. In this case, I knew someone would just wag a finger at me about the HR application being out of support. Thanks but no thanks.

Megan struggled to muster one last defense. “This program’s still running on a server somewhere!”

“For changes to existing code, the proper procedure is to file a formal change request through the Project Management Team. The PMT will create a billing code and assign appropriate resources, if they deem it a proper use of development time.” The boss then looked at me like I was a used tissue she was ready to throw in the trash. “If that’s all, I suggest you head back to Tech Support, Mr. … ?”

No way was I handing her my name on a platter. I tried to glance Megan’s way, but she was staring at her lap. I felt bad leaving her in that lurch, but staying would only make things worse for the both of us.

“Later,” I said, both a goodbye and a promise.

I got the hell out of Devsville, slipping down flight after flight of stairs with a lead weight in my chest. For a moment, Megan had brightened in the face of a collaborative challenge. I’d felt a little more alive, too.

Thank God someone had been there to make sure no one helped each other.

The same sicknesses plagued the joint year after year. Almighty budgets. Status quo worship. Hierarchy and miles of red tape. Promising young people like Megan had their spirits crushed, and schmucks like me just put their heads down. Still a huge pile of other support tickets waiting for me, after all.

The frustration and resentment, the desire to do something to fix this, burned in my chest like a bonfire.

Back at my desk, I found a message from Tony confirming the new printer was behaving at last. Bolstered by my friends’ findings, I closed his ticket with notes about how the resolution was only a band-aid on a gaping wound. I messaged Megan, too, thanking her for trying.

And with that emotional bonfire still raging, I settled in and typed out a long email to Leila, detailing in full the most recent shenanigans I’d been a part of.

By the time I finished, I had one bit of good news: Aggie, my old mentor who’d turned manager a few years back, had accepted my meeting request to meet the next afternoon.

She wasn’t my boss, which meant it was still safe to vent her way. I had every intention. My resentment would no longer let itself be buried under this or that technical hiccup. It was insisting upon action.


The next day, I walked to the downtown coffee shop well ahead of the appointed time, glad to have the excuse to be somewhere else. Bought myself a drink and sat down where I could watch the door.

Five minutes late turned into ten … then twenty. No Aggie.

It was totally unlike her. Sure, she’d canceled last-minute before, but she’d always got in touch with me to let me know.

Caffeine jitters fueled a fear I couldn’t shake off. I sent PMs and left voicemails on her cell and work phones, asking her to respond when she could. Back at work, I asked around the department, including my boss.

She’d been AWOL the whole day. No one knew what was up.

Hours of work still stretched in front of me. I could barely sit in my chair, much less look at my monitor.

Just call me, Aggie, I willed, staring at the cell phone clutched in my hand.

She didn’t. Not that afternoon, not that evening. Beneath my fear was this strange gut feeling, this knowing sense that my worries were justified. It was crazy, but there was no talking myself out of it.

Early the next morning, they roped the whole department into the big conference room. Nobody knew what was going on until a small, ashen group of managers, directors, and directors of directors filed to the front and called for attention.

I already knew, deep down, what was coming.

“We’ve we received some very unfortunate news,” one of the senior directors spoke. “Agatha Shaw … passed away in her home after a heart attack.”

A few gasps escaped the assembly. Otherwise, you could’ve heard a pin drop. Wide-eyed looks of shock surged through us like lightning.

Aggie.

My gut had known all along, but my brain still wasn’t having it. Somebody somewhere must have goofed up royal, I thought. Happens all the time in this joint. Aggie had more life and fight in her than I ever did. I—

“Those of you who reported directly to Ms. Shaw will report to Bill Watson for now,” the director continued. “Dismissed.”

The brass began showing themselves out.

The rest of us just sat there, too stunned to move. That’s it? I marveled. No memorial? No counseling? No time off? Not one drip of sympathy?

I could only look on helplessly as Bill Watson, my boss, walked right over to me. He put his hand on my shoulder, leaned over, and muttered into my ear: “My office.”

While my brain reeled, my feet stood me up obediently. They marched me right off to the next chair I dropped into, the one opposite the large desk in Bill’s office.

He settled in on his side. “It’s a real shame about Aggie. We’re screwed without her.”

I just sat there, still reeling.

”My manager is looking for someone to step up in a big way.” Bill nodded in my direction. “You’re ready. You deserve it. Her direct reports are reporting to me for now; over the next few months, I’m transitioning them to you. The promotion will follow, as soon as the next performance review comes around!”

We were just cogs in that joint. Never before had the point been driven home so viscerally. Righteous rage surged up from within, clearing my brain and shooting strength through my limbs. I jumped out of that chair and glared down at him. “Hell no! Find someone else!”

I got the hell out of there and dragged myself all the way home to collapse on my couch.


To be continued ...

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

Error'd: Einfach so

10 July 2026 at 06:30

Do you say "a FAQ" or "an eff eh cue"? Peter says eff eh cue I think.

"This is a test" Peter G. harrumphed testily. "Create an FAQ with exactly nine entries. Nine? Nine."

67f8e4c8a1e640379d670a4039d1e54b

"I think I spent over $NaN?" said an anonymous. "This was an interesting offer on myminifactory.com with tight expiry date. Didn't claim."

9b22730c427144faa646895be1411d53

And a different reader expected a speedy delivery anon. "It was about 23:10 UTC when I took this screenshot, the time zone I keep my PC in, yet local time my pizza was estimated to arrive at 19:05 CST. Naturally, I should expect to receive my pizza about four hours ago! Not my typical experience with delivery as of late, I must say, but a welcome change nonetheless..."

cd43a695c8e846cf858cc3b72ba77094

Super saver Michael R. lamented "That hurts, I missed 6 coupons that would have saved me 0%."

ec9a5b21636b4b4bad633abdb4393820

And our dragoncoder047 ground this out between his teeth. "Refactored the runtime spritesheet packer in a game engine I contribute to, and wound up with this extremely helpful error message. Turns out that the problem was the ggggggggggggggg wasn't properly detecting ggggggggg and was putting all the ggggggggggggg's in the same ggggggggggggggggggg. I think. Ggggggggggggg!"

189885dc898f4fe4b7ef095e9f1ef99e

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

Flushed Out

9 July 2026 at 06:30

While a project manager is frequently called upon for their planning ability, the real skill we want from project managers is their ability to communicate. The job of a project manager is to align the team doing the work, with the organization goals driving the work, with the management and leadership teams trying to understand the work, while juggling all the constraints like budgets, timelines, and the endlessly changing expectations for the project. A good project manager is worth their weight in gold. A bad one will cost their weight in gold.

Mark was hired on as a contractor, reporting to Tegan. Tegan was fresh out of business school, complete with an MBA and a variety of project-management training certifications. Unfortunately for Mark and the rest of the team, and especially unfortunately for Tegan, she had absolutely no real world experience. To make matters worse, this wasn't just a software project: they were working on a system which matched newly developed software with newly designed mechanics and custom build control electronics. A group of experienced software engineers, mechanical engineers, and electrical engineers all found themselves reporting to a bright and shiny MBA. It's a role that she probably could have grown into, but management saw all the acronyms she continuously put after her name, and decided she could just take the whole thing over with no real guidance.

It went badly pretty much from the beginning. Tegan was not a talented communicator. For example, Mark's team needed to know: on what timeline were the electrical engineers going to deliver the first prototypes, so the software team could start running bench tests of their software? Tegan's response was a fortune cookie message about balancing the complicated pipelines and lanes on the Gantt chart and hitting all of their milestones; like a fortune cookie, it was vague, important sounding, but ultimately empty.

Of course, the natural reaction amongst the engineers was to just route around the damage: the various teams could talk to each other just fine without going through Tegan. That, unfortunately, did not go over well with management. Tegan, as the project manager, was their insight into the project. They needed her in the loop on everything. And she couldn't just be informed, she had an MBA. She needed to be making decisions. But she was unqualified to make those decisions, which meant the project gradually ground to a halt. Tegan's emails got more vague, her meetings got longer but accomplished less, and after a certain point, she just stopped replying to key email threads.

The first few days of radio silence seemed like a gift. But as time passed and Tegan seemed uninterested or unable to reply to any of the questions the team had for her, the project started to flounder. The engineering teams escalated this problem to management. Management presumably went back to Tegan. At some point, feeling the weight of everything going wrong around her, Tegan sent out this email, which is definitely the best and clearest communication she managed during the project. It's arguably the clearest, and most accurate communication one could make in this situation:

Team,
I understand all the issues but there are complex interrelations that must be worked out. I am currently constipated on each issue and will let you know when there is movement.

  • Tegan
    MBA, CAPM, PMP

Her email cost the project many person-hours as all the engineering teams took a break to have a good laugh about the project manager admitting, in writing, that she was full of crap.

There was, eventually, movement. Tegan moved on to a new position at a different company. Her replacement, Pam, wasn't a new hire, but instead a transfer from another department. She wasn't a great project manager, certainly not worth her weight in gold, but she had enough experience to avoid the worst mistakes, and most important: she was good at regular communication in order to keep things moving.

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

CodeSOD: Module Test

8 July 2026 at 06:30

TJ inherited a NestJS project. The original developers left the team many years ago, but they've left their mark in the codebase.

// ProjectsModule.ts
@Module({
  controllers: […],
  providers: […],
  exports: […],
})
export class ProjectsModule {}

NestJS is a dependency-injection oriented framework for TypeScript code. It offers "providers" (dependencies that can be injected), "controllers" (as one would expect), and lets you bundle them together into "modules". Modules can depend on other modules, letting you build a modular and flexible graph of dependencies. This means that the empty module isn't wrong here.

No, for it to be wrong, we need to write some tests:

// ProjectsModule.test.ts
describe("ProjectsModule", () => {
  it("can be created", () => {
    const projectsModule = new ProjectsModule()
    expect(projectsModule).toBeTruthy()
  })
})

Since modules are just containers for related code objects, there isn't much to test here. While "dynamic modules" which execute code are a thing, they don't execute that code at construction time anyway. This test will always pass. It isn't a test, it doesn't do anything. It likely doesn't even get their coverage up, since whatever providers or controllers it's referencing aren't covered by this test. It's a test that tests nothing but the framework it runs on top of.

"At least there are tests," TJ writes.

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

CodeSOD: On Hold

7 July 2026 at 06:30

"Dragoncoder" supports a web application that has a "wait time" for access. I hate that that's a thing, but I recognize that there are real-world constraints where this might make sense. Still, I hate it. But that's not the WTF.

      var minutes = parseInt( 12 , 10);
      var time = document.getElementById('waitTime');

      if ( minutes < 2) {
        time.innerText = "Your estimated wait time is 12 minute."
      } else if (minutes < 60) {
        time.innerText = "Your estimated wait time is 12 minutes."
      } else if (minutes === 60) {
        time.innerText = "Your estimated wait time is 0 hour."
      } else if (minutes < 120 && (minutes % 60 === 1)) {
        time.innerText = "Your estimated wait time is 0 hour and 12 minute."
      } else if (minutes < 120) {
        time.innerText = "Your estimated wait time is 0 hour and 12 minutes."
      } else if (minutes > (60 * 4)) {
        time.innerText = "Your estimated wait time is more than 4 hours."
      } else if (minutes % 60 === 0) {
        time.innerText = "Your estimated wait time is 0 hours."
      } else {
        time.innerText = "Your estimated wait time is 0 hours and 12 minutes."
      }

This wait time page is initially rendered by their backend, but after that point, gets served up by a cache at the edge of their CDN. That makes sense, since "have the users hammer your backend while they're waiting" is a bad idea.

Note the line var minutes = parseInt( 12 , 10);. This is rendered from the backend, which is of course my least favorite way to send data from the server side to the client side.

But that's not the core problem here. The core problem is: what the hell are they outputting?

If your wait time is less than 2, or less than 60, we tell you that your wait time is 12 minutes. Or "12 minute", because who cares about pluralization? If your wait time is exactly 60 minutes, we tell you that your wait time is "0 hour", which I assume means you'll have enough time to watch the classic airplane disaster movie, Zero Hour, which you surely know Airplane! is a remake of.

I can only think that the text is also being generated by logic on the server side- though our submitter doesn't suggest that's the case. Though they do wonder why the code couldn't be something like: Your estimated wait time is: ${Math.floor(wait_time_minutes / 60)} hours and ${wait_time_minutes % 60} minutes, which is both fewer bytes to send from your cache and more useful to the end user.

Or maybe we just make this wait time go away. Again, I don't know why it's there, there may be a good real-world constraint that requires it, but… is there? Is there really?

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

Best of…: Classic WTF: Difficult Personality

6 July 2026 at 06:30
As the US took this weekend to celebrate their complicated relationship with tyranny, we reach back through the archives for another story of tyrants. If you think about it, the Declaration of Independence is basically the same thing as quitting without notice. Original --Remy

It was Steve's first week on the job, and he had plenty of questions about the code base and the new features he was supposed to implement. He muddled through for most of the week, but Friday morning he hit a brick wall and needed to talk to Bill, the architect.

"Can I meet with you for like an hour to go over things?" Steve asked.

"No."

"Can I get half an hour then? I h-"

"No. Company meeting, every Friday, 12-5pm. It should be on your calendar. I'll forward the invite."

Bill also couldn't free up time in the morning, so that meant Steve was stuck until Monday afternoon. Still, it probably wasn't all bad. He assumed that since this was a small company, in startup mode, it was going to be one of those meetings that was less meeting and more party. He had heard about one company in town that had a kegger every Friday afternoon.

Steve really should have known better. During his interview, the actual technical questions were thin on the ground. It focused more on "soft skills", like time management. He fielded a lot of questions about how best to manage his time. The other question that really stuck out in his mind was the standard, "Have you ever had to deal with a difficult personality in the workplace? How did you deal with it?" It was memorable, less because the question itself was unusual, but because at least six variations of the same question showed up in the interview.

On his very first day, he learned who the difficult personality was: Frank, the boss and grand-high pooba of the dev team. Around 2PM Frank lumberghed himself into Steve's cube. "Yeah, we've got a little problem," Frank said. "I've noticed you spending a great deal of time in the break room."

"Oh, yeah, I was just going back for more coffee," Steve said with an awkward laugh. "You know how it is with programmers- we're fueled by caffeine."

"Yeah, well, if you could just go ahead and make sure you're at your desk doing work, that would be great."

As it turned out, Frank had gone easy on Steve because it was Steve's first day. The next day, Steve sat in on Bill's planning meeting- a 4-hour marathon to organize the development backlog and parcel out work. Halfway through, Bill called for a break. He and a few other co-workers darted outside to gradually commit suicide via cancer, while everybody else hung around the room committing suicide by donut. And then Frank walked in.

"What's happening?"

"It's um… just a little break," one of the devs replied. "Bill's outside."

"I see." Frank loitered in the room until Bill returned. The instant Bill's foot crossed the threshold of the meeting room, Frank's human facade was stripped away, and a spitting, slavering demon replaced him. He proceeded to dress Bill down, back up and right back down for disrespecting his team, disrespecting the company, disrespecting Frank and Frank's poor elderly mother with his attitude. He closed with, "They're developers and I want them sitting around and developing! Not waiting for you to finish your smoke breaks!"

On Thursday, Steve got to drive a meeting to show off the latest batch of features the dev team had completed. When he turned on the projector, Frank asked, "What's wrong with your computer?"

"Um… nothing?"

"The desktop is wrong! None of the icons are in the right place!"

Like most developers, Steve had changed the wallpaper and reorganized his desktop to suit his working style. Unfortunately, his transgression against the default desktop settings set Frank off on a long rant that consumed the entirety of the meeting. Steve was lucky, Frank claimed, that he wasn't fired on the spot. Standard work was vitally important, and personalization was frowned upon. "It's vitally important that any developer can use any other developer's computer- we can't afford to waste a minute of time just because you needed to be a special little snowflake!"

By the time the Friday afternoon meeting rolled around, Steve should have been expecting some kind of Frank-led time management course. Instead, Bill handed him a mop. "New blood gets mop duties. Start in the break room, and then hit the other common areas."

"Excuse me?"

"Frank's orders. Every Friday, we spend the afternoon cleaning the office, from top to bottom."

"There isn't a cleaning crew?"

"Oh, there is," Bill said. "Frank doesn't trust them to do a good job." That weekend, Steve decided to take Frank's lessons on time management to heart, and immediately left to seek employment that didn't involve wasting his time.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

Error'd: Fi fa foe

26 June 2026 at 06:30

First up this week is a little story about a fifafail. I do wonder if this was a failure of the television station, or whether there was something more to it than that.

Hercules wrote to alert us to these World Cup shenanigans, explaing "At least the flags were correct. And yes, this was live TV. The host got the country names correctly, and even called out that the written text was wrong"

5503f1d7141948f88f4650c17468ff46

"I'm very open in my job search but I did limit it to France. The search has been working well for months, but this morning I got a bevy of new interesting propositions. It seems France is much bigger than it was yesterday." Apparently WorkerNumber29200 is surprised by the expansionist nature of an imperialist coloniser. Plus ça change, Worker.

f05fd09185bd4f2ebeacc92de9609131

We have a couple of wtfs from Github. First Hans K. "would love to find a, so I could fix this GitHub Dependabot issue."

4d41995a2e7a423cbf8de6e960b876c4

And Peter S. figures that "GitHub has trouble doing basic math -- or they have an unpublished proof that 0=1"

e954781458d64f6da02bfad014efc854

Finally Michele has just encountered one of the most maddening phenomenon on Amazon recently. "Searching for a cheap USB-C fast charger. Got a list of expensive CDs of obscure artists." All of them AI-generated, like the 100000 Whys books?

cdf7c2baae7541a9ad8e90b9c4ecb0c4

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

The Roadmap

25 June 2026 at 06:30

When Gary was called in for a meeting with a few of his managers- because of course he had several- he thought it was going to be for an "attaboy", because things had been going really well for the past few months.

Gary had inherited a mess, and taken over a nightmare application. It was the kind of application that should be a simple CRUD-style data-driven app, but somehow despite only having 20ish entities it managed, someone had generated 500+ controllers for managing them. Most of those controllers were copy/pasted code with minor changes in the WHERE clause of a SQL query.

And that was just the code. The infrastructure was similarly a mess, with duplicate resources provisioned in their cloud host. There was no CI/CD, no unit tests to speak of, no deployment process that wasn't "manually copy these files and pray". And uptime? You've heard about "five nines", but this product was lucky to get even one nine. Especially because the manual deployment process meant a few hours of downtime.

And that was just the infrastructure. The backlog was similarly messy. There were lots of tasks- many thousands- but not a single one had a priority. Most of the tasks were something like, "Fix database timeouts", or "Bug 531" with no description to explain what they were. At best, some of the "new feature" tasks linked to a Google Doc that explained a software roadmap that had been last updated in 2020.

So with no guidance, Gary and the rest of his team got to work. Cloud costs were massive. Just cutting the duplicate resources would help, but with actual planning it wasn't hard to find even bigger wins. In total, Gary got the cloud costs down 60%- essentially saving the company a small multiple of his salary every year.

With that out of the way, getting a CI/CD pipeline running was next. Within a few weeks, manual deployments were gone. Everything was automated. Downtime nearly vanished. And now, with all the cost savings in cloud resources, for a fraction of what they were paying, it was easy to automate provisioning test environments for each new feature.

So Gary was very ready for some congratulations when he sat down with management. He was prepared to discuss all the wins he and the rest of the developers on the project had gotten over the past few months.

"I'm sure you know why we're sitting down," Manager the First said when they settled into the conference room.

"I'm sure," said Manager the Second.

"We have some concerns about your performance," Manager the Third said.

"My performance?" Gary asked.

"Yes," said Manager the First. "Let me pull up the backlog."

"And the roadmap," said Manager the Second.

"Yes, I'm getting that up too, thank you." The trio of managers struggled with pulling up the appropriate pages, and after about 15 minutes, gave up. Instead, they discussed their complaint without visual aids. "You haven't completed any of the tasks on the roadmap. Bug 673 has been open since you started on the team. None of the roadmap milestones have been touched. There's absolutely no progress."

"Okay, but that document was wildly out of date," Gary said. "Instead I put cycles into solving the actual problems we're having. I've saved the company a huge amount of money. I've gotten our development cycle time down to a fraction of what it was. And we have basically no downtime!"

"That's all very nice, I'm sure," said Manager the Third. "But none of that was on our roadmap."

"Well, maybe we should set up a meeting to go over the roadmap," Gary said. "Because a lot of the tasks on there don't make much sense right now-"

"I don't think that's a good use of time," Manager the Second said. "Large meetings are expensive. Just stick to the roadmap, please."

With that, the meeting ended. Gary went back to work…

… updating his resume.

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

CodeSOD: Authorized Logger

24 June 2026 at 06:30

Gretchen's company recently got purchased by Initech. Specifically, they were bought for their dev team, of all things. They had a few software products that were high performers, and Initech wanted that secret sauce. They bought the company, and then split the dev team up and migrated the developers to new products.

That actually worked out okay for Gretchen, most of the time. For a few projects, the dev team was given some requirements and a free hand to figure out how to deliver them. They were free to reuse code that existed or rewrite entirely, based on their own judgement. They were free to pick the tools they wanted to use, and the results worked out well.

But there were some projects that… were a different story. After those successes, Gretchen got moved onto a project that was 90% firefighting. The app had code like this:

req.body.externalId = !!req.body.externalId ? req.body.externalId + "" : "";

How's that for some null handling.

The whole thing can't run on a version of NodeJS newer than 14: a version that last got an update in 2023.

"The code follows no conventions," Gretchen writes, "there's no logging."

exports.create = (req, res) => {
  logger.debug('creating new staffClient');
  logger.debug(req.body)
  // let staffClient = new StaffClient({});

  // // run through and create all fields on the model
  // for(var k in req.body) {
  //   if(req.body.hasOwnProperty(k)) {
  //     staffClient[k] = req.body[k];
  //   }
  // }


  StaffClient.query().insert(req.body)
  .returning('*')
  .then(staffClient => {
    if(staffClient) {
      res.send({success: true, staffClient})
    } else {
      res.send({ success: false, message: "Could not save StaffClient"})
    }
  });
}

Now, you may say to yourself, "What do you mean there's no logging? I see it right there!" There is a logger utility class, and do you know what it prints when you call logger.debug("some message")? It prints DEBUG.

This code handles an HTTP request, and stuffs the body of the request into the database; here's hoping that it's a well formed request. Somebody's got a lot of faith in their front end. WHat's interesting about this one is they've tried two different ways of copying the request object into the database, the first one focusing on making sure they only copied non-inherited properties, and the second just YOLOing the data into the database.

Now, this particular segment goes through their ORM to write data into the database. But not all the code does that. Many places write data through direct SQL, and guess what happens there: SQL injection vulnerabilities.

You may also notice that this function doesn't do any authorization checks, which is fine, that should be configured in the middleware. Should be- but isn't. Most endpoints have no authorization checks at all. Even the endpoints that do, like their admin API, have copies of the same endpoint with no authentication configured.

[Advertisement] Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready.Learn more.

CodeSOD: Do a Lot to Do Nothing

23 June 2026 at 06:30

Today's anonymous submitter works in finance. I'll let them start the introduction:

This is a legacy application that has been running for nearly a decade in production so one could say that it's been thoroughly tested by daily production use and nothing needs changing

This is a collection of two C# methods, and we'll start with ValueAGPFund, which isn't a WTF per se, but definitely not code I'd want to maintain either.

public Valuation ValueAGPFund(int valuationId, ValueAFundParameters parameters, CapitalAccount capitalAccount, int? lotId)
{
    if (parameters.UseActiveCoefficientSet)
    {
        parameters.CoefficientSet = _coefficientSetQueries.GetActive();
    }
    parameters.InternationalDveCoefficientSets = _coefficientSetQueries.GetInternationalDveActive();
    var referenceData = _referenceDataFactory.CreateReferenceData(parameters, capitalAccount);
    if (lotId != null)
    {
        var di = referenceData.FundDirectInvestments.Where(x => x.PositionId == lotId);
        referenceData.FundDirectInvestments = di;
    }

    var countryMappings = _countryQueries.GetFullIsoCountryList();
    var valuation = _valuationFactory.Initialise(referenceData, parameters, countryMappings);
    valuation = ApplyValuators(valuation, referenceData, _valuatorFactory.CreateValuators(valuation, this));

    var valuationForCoverage = _valuationQueries.GetWithDirectValuationsAndFundValuations(valuationId);
    valuation = ApplyCoverage(valuation, valuationForCoverage);

    foreach (var fv in valuation.FundValuations)
    {
        _logger.Info($"Debugging distributions: for fund (parameter fund id = {parameters.FundId}, valuation fund id = {valuation.FundId}, fund valuation fund id = {fv.GpFundId}) in valuation {valuationId}," +
            $" loaded fund investment distributions from {string.Join(", ", fv.FundInvestmentDistributions.Select(x => $"{x.InvestmentId}:{x.TransactionDate:yyyy/MM/dd}"))}");
    }

    foreach (var fv in valuation.FundValuations.Where(x => parameters.InvestmentIds.Contains(x.EqtInvestmentId)))
    {
        fv.ValuationId = valuationId;
        _fundValuationCommands.Add(fv);
    }

    foreach (var dv in valuation.DirectValuations.Where(x => x.LotIdDiOnly == lotId))
    {
        dv.ValuationId = valuationId;
        _directValuationCommands.Add(dv);
    }

    foreach (var vw in valuation.ValuationWarnings)
    {
        vw.ValuationId = valuationId;
        _valuationWarningCommands.Add(vw);
    }

    var previousValuation = CheckPreviousValuationIfRequired(valuationId, parameters, capitalAccount, lotId);

    if (previousValuation != null)
        valuation.ChildValuations.Add(previousValuation);

    if (parameters.Frequency == ValuationFrequency.Daily)
    {
        var unapprovedValuations = _valuationQueries.GetList(valuation.FundId, valuation.ValuationDate, valuation.Frequency, valuation.Purpose)
                                                    .Where(x => x.IsApproved == ValuationStatus.Unapproved)
                                                    .ToList();

        _valuationCommands.Delete(unapprovedValuations.Select(x => x.Id).ToArray());
    }

    valuation.Id = valuationId;
    _valuationCommands.Update(valuation);
    _valuationCacheService.Refresh(valuation.Frequency, true);

    return valuation;
}

The key problem with this function is that it's got loads of side effects. It modifies the parameters parameter, which while it was passed by value, the value itself is a reference, so you are updating it on the caller, whether the caller likes it or not. It also modifies a bunch of internal class members. It's also just… doing a lot of different steps. It's not a WTF, but it's bad code. Note the call in the middle to CheckPreviousValuationIfRequired- we're going to come back to that in a second.

Let's take a look at how it's called.

private Valuation CheckPreviousValuationIfRequired(int valuationId, ValueAFundParameters parameters, CapitalAccount capitalAccount, int? lotId)
{
    if ((parameters.Frequency == ValuationFrequency.Quarterly || parameters.Frequency == ValuationFrequency.Monthly)
        && ValuationPurposeHelper.UserGenerated(parameters.Frequency).Contains(parameters.Purpose))
    {
        var inPeriodParams = new ValueAFundParameters
        {
            FundId = parameters.FundId,
            ValuationDate = parameters.ValuationDate.GetPreviousValuationDate(parameters.Frequency),
            CreatedBy = parameters.CreatedBy,
            Purpose = ValuationPurpose.InPeriodCalculation,
            Frequency = parameters.Frequency,
            InvestmentIds = parameters.InvestmentIds,
            UseActiveCoefficientSet = true,
            UseAmericanDve = parameters.UseAmericanDve,
            ValuationOptions = parameters.ValuationOptions
        };

        var openingValuation = _valuationQueries.GetInPeriodOpeningValuation(inPeriodParams.FundId, inPeriodParams.ValuationDate, valuationId);

        //return openingValuation == null
        //       ? null
        //       : ValueAGPFund(openingValuation.Id, inPeriodParams, capitalAccount, lotId);
        return openingValuation == null
                ? ValueAGPFund(openingValuation.Id, inPeriodParams, capitalAccount, lotId)
                : null;
    }

    return null;
}

This function checks the input parameters. Depending on the values, it will either return null, or it will call ValueAGPFund. Wait a second, ValueAGPFund calls this function. That's not good.

But let's really focus in on the return statement and its comment:

        //return openingValuation == null
        //       ? null
        //       : ValueAGPFund(openingValuation.Id, inPeriodParams, capitalAccount, lotId);
        return openingValuation == null
                ? ValueAGPFund(openingValuation.Id, inPeriodParams, capitalAccount, lotId)
                : null;

The current version checks if openingValuation is null, and if it is, tries to access it, thus triggering a NullReferenceException. This function either returns null or throws a NullReferenceException. So all that worrying about side effects and circular calls doesn't matter, but this likely isn't correct. The comment indicates that there used to be a correct version, which only called ValueAGPFund if the valuation wasn't null- but that version likely had all the problems of circular calls and unpredictable side effects.

As it stands, the application as a whole works. Since CheckPreviousValuationIfRequired only ever returns null or throws an exception, and since ValueAGPFund is only called from here, it looks like these functions could just both be removed without problems. But our submitter is wary of doing that:

The problem is that I first need to figure out whether 1) this piece of code produces any side effects and 2) nobody is relying on the System.NullReferenceException being thrown here.

No worries, though, right? I'm sure your unit tests will catch any regressions caused by removing that. Because this is the kind of code that definitely has great unit tests.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

CodeSOD: When False is True

22 June 2026 at 06:30

Lillith was integrating some new tools into an existing Ruby on Rails API. The existing API allowed you to send a dry_run flag along with the request, so that you could have the service calculate its changes without applying them.

The problem was, the new tool Lillith was integrating could send, in the body of the request, {"dry_run": false}, but the service would see it as true. Consistently.

The helper method which checked for "true" parameters looked like this:

def param_true?(param_name)
  param_value = params[param_name]
  params.key?(param_name) && (!param_value || param_value.to_s.downcase == 'true')
end

The purpose of this function is to handle stringy or nil inputs gracefully. And there's one thing I can say about the function: it will always identify a true value correctly. If your false value is a string, "false", it also works. But that pesky !param_value mean that any actual boolean false value will be seen as true.

This function has been in wide use through the application. Lillith's best guess is that up to this point, no one had set the dry run flag on anything but GET requests, where everything was strings. On POST/PATCH/PUT requests, where the data was passed in the body as JSON, it got parsed into actual boolean values, and thus failed.

That's the WTF, certainly, that this function was lurking and waiting to cause this confusion. But the annoying thing in this function is that it fetches the value from the associative array, then calls params.key? to see if the key exists. That's fine, since Ruby just returns a nil if a key doesn't exist, it's just annoying. I hate to see it. This is, admittedly, more of a "me" problem, but I hate it.

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

Error'd: Microbits

19 June 2026 at 06:30

This week we have got a couple of Mathanon's. Maybe they're the same person, maybe they're not, there's really no way to know!

Frist anon has a "Numeric fun fact" for us: "Got a form sent from work to express interest in some event. They actually enforced the validation that the answer must be a number, so I submitted "42"." Bravo.

97007ddf5e024b1387e4f04fea560955

Next anon has a different numeric fun factor: " The SAS website wants us to know the size of the file behind the link down to the nanobyte precision." They split the bit! That must be what this quantum computing thing is about.

0a662a2420f54deab7167aa5e92a5bf3

Conscientious dad Mark R. takes all the responsibilities. "My kid's school ensures they're legally covered on all things said and unsaid."

55a99d3a5f5b4e588c8dd17063de6058

Philipp H. points out "The Redmond philosophers have created something the old Greek philosophers will have to rethink. Or is this a pun on Schrödinger's Cat? German→English translation: "We cannot bring/transfer/switch you to this message because you're in a chat, in which you're not in.""

35d852325db54dbdbbe52fff86de5cc7

We haven't heard from Michael R. in a while. Here he is with a pithy "The irony is not lost on me."

a045bde3e7c94088ae041de113a1d58a

Happy Juneteenth to those who celebrate.

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

Representative Line: Sort This Out

18 June 2026 at 06:30

Today's anonymous submitter has spent a long time toiling through many, many tickets. Their effort has been an attempt to "save" their employer from the disaster left behind by by a highly-paid consultant. As one does, our submitter started with the highest priority tickets with the highest severity. Eventually, they whittled down that list, and had some bandwidth to start looking at the pieces of the code which clearly weren't exploding right now (because there were no tickets), but were likely to explode at some point in the future (creating a storm of tickets).

Scanning through the JavaScript, our submitter found a sort function. That was automatically concerning- why was that particular wheel being reinvented?

The first line of the sort function was this:

obj[x._id.account_id] = x.count_total

In this case, x._id is meant to be the unique identifier from their Mongo DB. That, uh, should be not precisely a UUID (Mongo does its own weird version), but it definitely shouldn't have an account_id field on it. They are storing an arbitrary object as their unique identifier in the database. Which, I'm no Mongo expert, but I don't need to be Flash Gordon to know that's a bad idea.

But setting aside the choice of using random objects as unique identifiers, there's also the other question: how is this furthering the goal of sorting? Why on Earth am I building an object in the form: {"id0": 5, "id1": 7, "id2": 11}? Or am I even doing that? This is the first line of the function, so we're not even doing a loop, it's just {"id0": 5}.

This isn't just an unexploded bomb, it's a mystery: the primary mystery being why hasn't this exploded already? The second mystery is: what's going to happen when your luck runs out?

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

CodeSOD: Weekly Calculated

17 June 2026 at 06:30

There's a language out there called "Progress Advanced Business Language" (or "Open Edge Advanced Business Language"). Just hearing that string of words in a sequence tells you you're in for it. It's a verbose, "English-like" programming language. But we're not here to pick on the language.

A long time ago, Mirjam had the "pleasure" of working in a Progress ABL environment. At some point, one of the developers had needed to find a date six months prior to the current date. It didn't need to be accurate, and thus said developer littered the code with comments reminding everyone that it didn't need to be that accurate. They arguably spent more time defending the choice to be inaccurate than it would have taken to write code that would have been accurate.

Mirjam doesn't have the code anymore, so what we have here is a mix of her remembered pseudocode, Progress syntax, and my attempts to clarify all of it. Let's not worry too much about the language, and instead focus on the logic:

ASSIGN v-date = TODAY.

/* Calculates the current week/year */
RUN week.p(INPUT v-date, OUTPUT v-weeknumber, OUTPUT v-year).

IF v-weeknumber > 26 THEN 
    ASSIGN v-weeknumber = v-weeknumber - 26.
ELSE 
    ASSIGN v-weeknumber = 52 - 26 - v-weeknumber
           v-year       = v-year - 1.

/* Turn the result of that calculation back into a date */
RUN week2.p(INPUT v-weeknumber, INPUT v-year, OUTPUT v-resultdate).

This code gets the current date. It then breaks that into week number of the date (1-52), and the year of the date. Then, if the week number is greater than 26, it subtracts 26 from it, giving us a date half a year ago, ish. If the current weak number is less than or equal to 26, we do 52 - 26 - v-weeknumber, and decrement the year. Which yes, is a fairly round about way to handle the rollover- 52 - 26 happens to be… 26.

It's worth noting, that as primitive looking as this syntax is, Progress ABL does have an ADD_INTERVAL function, which lets you do date arithmetic, without all this nonsense. In fact, Mirjam went ahead and replaced all of this with a single line.

That said, as a little bonus WTF, Progress does have some weird date quirks, for example, you can construct a date from an integer. Which has a very unsurprising (but also, weirdly surprising) range of possible values:

The value of the expression cannot be a date value before 12/31/-32768 or after 12/31/32767.

At least that covers a range that includes both the discovery of agriculture and the eventual rediscovery of agriculture after the event that enters into legend as "The Fall".

[Advertisement] Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready.Learn more.

CodeSOD: Required Fields

16 June 2026 at 06:30

If you want to connect to another system, you need to supply credentials. That's a pretty obvious requirement. We can set aside the whole technical challenge of managing those credentials and the security problems various techniques create, and just focus in on: you must supply some credentials to authenticate.

Lisa has inherited a method which connects to another system. It, correctly, will complain if you don't supply parameters for credentials. It will, incorrectly, mislead you about their requirement:

public function connect(string $username = "", string $password = ""): void
{
    if ($username === "") {
        throw new InvalidArgumentException("username is required.");
    }
    if ($password === "") {
        throw new InvalidArgumentException("username is required.");
    }
    // ... other stuff
}

The $username and $password fields here are set to default values. Which means it is syntactically valid to invoke the function connect(). It won't work if you do that, as it will definitely throw an exception, but this is a bit of misleading ergonomics. If the parameters are required, they should probably, I don't know, be required?

What really draws our attention here, however, is not the misuse of default parameters, but the absolute disaster that debugging issues with this function could easily become. If you fail to enter a username, you'll get an exception telling you "username is required". And if you fail to enter a password, you'll also get an error message telling you "username is required".

Which is a factually true statement: username is required. But it's not the cause of my error, which is that I failed to supply the password. Theoretically, though, we could adopt this to make writing exception messages easier. I could make every exception message be "username is required", and it wouldn't be wrong. And clearly, that's what we truly mean when we say "not even wrong".

[Advertisement] Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready.Learn more.

CodeSOD: Caught a Mistake

15 June 2026 at 06:30

Daniel recently started a new job. His first task was to fetch some data from the database and render it to the user. Easy enough, and there were already wrapper functions around the database to make it easy. He called execute_read, passed it a query, and checked the results.

There were no results. But the query definitely should have returned results. What was going on?

def execute_read(conn, query, params, only_one=False):
    result = None
    cursor = None
    try:
        start_time = time.time()
        cursor = conn.cursor()
        cursor.execute(query, params)

        if only_one:
            result = cursor.fetchone()
        else:
            result = cursor.fetchall()

        end_time = time.time()
        time_taken = end_time - start_time

        if env.is_production():
            if time_taken > 0.4:
                logger.critical("long query", query=query, time_taken=time_taken)
        else:
            if time_taken > 0.2:
                logger.warning("long query", query=query, time_taken=time_taken)

    except Exception as err:  # pragma: no cover
        logger.exception("execute_read exception", exception_msg=err, query=query)

    finally:
        logger.debug("execute_read debug", query=query, params=params, only_one=only_one)
        if not result:
            if only_one:
                result = {}
            else:
                result = []
        if cursor:
            cursor.close()

    return result

There are a lot of things I don't like about this function. The only_one parameter, for starters. Note how the database library actually breaks that behavior out as different functions- that's a much more appropriate model, especially since you have wildly different return types depending on how that flag is set.

Similarly, checking env.is_production() to check a timing threshold is itself pretty awful. I can sympathize with wanting different timing constraints based on what environment you're in- but if that's the case, the timing constraint is the parameter. env.long_query_threshold should be the configuration parameter. Also, your database should be able to alert you to these kinds of things, so that it doesn't live in your code anyway.

But the WTF here is the promiscuous exception handler, which catches all errors and simply logs them. This created a situation where Daniel sent a query to the database and got no results. He didn't go straight to the logs and tried to debug it more directly, so it took him quite some time to find the execute_read exception log line which told him what was wrong: his SQL query had a syntax error.

Daniel writes: "I can't imagine the disaster that this causes if there's a network hiccup in production." Failing silently and returning empty results sets definitely is inviting a lot of confusion.

[Advertisement] Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready.Learn more.

Error'd: No Rush

12 June 2026 at 06:30

This week, friend Adam R. sent in an entry and included with it a link to a short-form YouTube video. Presumably this was a mistake, because I watched that video and the next one and the next one and the next one and after two hours I still haven't got this column ready. I won't share the video link with you. You're welcome.

What Adam really wanted to say was: "The USPS offers a sincerely service called Informed Delivery that, every morning, emails you scans of the exterior of your postal mail that you're expected to receive that day, which is a genuinely useful service (#not-sponsored). In today's digest, however, the subject line had an extra None thrown in there. Some Python script gone wrong that wasn't tested before production, perhaps?" We get lots of NaN, null, and undefined submissions, but None are actually rare.

1b83568aee6945e79583f845c9c25d80

Carlos sent us a fresh email, reporting "Mint Mobile hit the jackpot but their template engine didn't."

1866069ff694449e92574129d7315e0a

"No Rush" stated Robert F. calmly. "My Carbonite backup files will be deleted in 11250001 days if I don't reconnect the drive. Well, there's no rush, really. They have given me 30,822 years to reconnect it. (It was never disconnected in the first place!)"

72788c6d3e3944a7b6cf6aa056ed4c3a

"Roosting indeed" harumphs The Beast in Black. "Somebody should tell Claude Code that it keeps using that word but I do not think it means what it thinks it means. On the other hand, considering how sssllllllooooooowwww it usually is, perhaps this is honesty."

7a4c719bb94a4c66843d2a548b93de1e

Peter S. has been driven to madness by Sixt, right along with me. "Now that I am silver, Sixt's top offer is to fill all mandatory fields in their data extension. I wonder what gold gives me."

f4936743eb534c85804e190ef3b5a37c

[Advertisement] Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready.Learn more.

CodeSOD: Dating in Hungarian

11 June 2026 at 06:30

A horse can only be so tenderized, but as well established at this point: I don't like Hungarian Notation. Richard G sends us an example of yet more of it, being misused, as well as some bad date handling. That's basically two of the easiest things to complain about, so let's take a look!

DateTime sCDate2 = Convert.ToDateTime(Hdn_SelectedDate.Value);
Double dStart2 = double.Parse(Hdn_SelectedShifts.Value.Split('@')[0]); // Gets something like "10.5" for 10:30

// More code ...

DateTime lSelectedStartAdd = DateTime.Parse(sCDate2.ToShortDateString() + " " + DateTime.FromOADate((dStart2) / 24).ToShortTimeString());

We take the value of Hdn_SelectedDate, which is one case where I'm actually willing to be a bit flexible on my hate of Hungarian Notation. In this case, it tells us that this is a "hidden" field on an ASP .Net form. Of course, storing a bunch of data in hidden fields on your form is a dangerous pattern, and in this case, they're carrying between 30 and 50 different pieces of data from one page to the next as hidden fields.

In any case, we take the value of that field and convert it to a datetime, storing the result in sCDate2. Here, the questions start. s, conventionally, tells us that this is a string. But it is not a string, it is a date. Why is it CDate? Actually, why is it CDate2? What's so 2 about this? There is no sCDate, sCDate1, or any other variation thereof- why 2?

Then, we look the contents of Hdn_SelectedShifts. This is another hidden field, and this one stores a string that is delimited by @s. We take the first element, which represents a time of day- as a double. 10.5 means 10:30. That's certainly a way to represent a time of day.

With this data in hand, we then use this to populate the lSelectedStartAdd variable. Once again, the l exists to mystify us. In some Hungarian flavors, it could mean "local variable", but if that's the case, why aren't we using that for any of the other local variables? More commonly, it might mean "long integer", but once again: it's a date.

This all brings us to DateTime.FromOADate. No, this is not when you Netflix and chill while watching cheap streaming sci-fi, OA in this case stands for OLE Automation, and now we have to go down a rabbit hole which has nothing to do with any of this code.

One of the things which made Windows what it was was the use of COM; the Component Object Model was an object oriented approach for letting applications talk to each other. It's what gave us DLL Hell, but it was also a really powerful system for automating software. You could use Visual Basic to leverage COM libraries provided by other software; even if the software you were targeting didn't have a scripting system, you could write your own scripts to control it anyway. OLE, Object Linking and Embedding, was a subset of all the COM functionality. It replaced Dynamic Data Exchange, which was the previous way of automating applications. With COM, COM+, DCOM, DDE, OLE, Microsoft created a whole soup of ways to link to functionality exposed by other applications. It was a giant mess, and I just put this paragraph here to flashback on the horrors of that era.

In any case, because OLE was mostly about automating Office applications, and because of Remy's Law of Requirements (no matter what the users said they want, what they really want is Excel), OLE Automation has its own date data type, which is a floating point number measuring the offset from December 30th, 1899. Which, of course, is not Excel's date epoch: Excel starts at 31-DEC-1899. Except Excel inherited its epoch from an older spreadsheet tool, Lotus 1-2-3. And Lotus had a bug: it thought 1900 was a leap year. Which means in practice, for any date past 28-FEB-1900, the effective epoch is 30-DEC-1899. Excel intentionally recreated the bug, because it needed to be compatible with Lotus 1-2-3 if it had any hope of competing in the market. One pesky little detail and now 1900 is a de facto leap year.

I'm sorry, we've got afield. We have dStart2, which is a floating point number representing hours in the day, with minutes as the fraction. We divide that by 24, then pass it to FromOADate, which will now treat that as an offset from 30-DEC-1899 00:00:00, giving us a date like 30-DEC-1899 10:30:00. We grab the time string off that, the date string of four date, munge them together and parse it back to a date.

Of course, the C# DateTime type has an AddHours, so they could have just done scDate2.AddHours(dStart2) and skipped all the parsing.

You want to know something more fun about this? That floating point representing time? It's initially populated by having users select off a drop down, and the drop down uses as its labels the more conventional HH:mm format. The value stored by the drop down is the floating point value. And yes, someone did manually write all that out in the code, they didn't use a loop or anything.

In any case, this is a long winded reminder: I hate Hungarian Notation.

[Advertisement] Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready.Learn more.

CodeSOD: Delicious Fudge

10 June 2026 at 06:30

Stella (previously) sends us a much elided snippet. The original code is several thousand lines contained in a single try block. But the WTF is pretty clear without seeing all of that:

try:
  # the whole business logic without any exception handling
except:
  print("Fudge")

They didn't really say fudge of course, but we mostly try to keep profanity off our main page. Mostly. In any case, when your operation fails someplace in the middle and you have no idea where, why, or how: "Oh, fudge!" is the appropriate expression.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

CodeSOD: Driven Development

9 June 2026 at 06:30

We should always be wary of "(.+)-driven development". Things like test-driven development, or domain-driven development are fine, but they're also frequently approached from a perspective of dogma, which creates its own terrible outcomes.

But let's talk about domain-driven development. Without getting too bogged down into the details of the approach, the idea is pretty straightforward: describe you domain model without reference to any lower-level concerns, so you can effectively write your domain logic in an abstract language tuned to your specific needs. In other words, it's just a pretty good practice. DDD offers tools and techniques for doing it, and as stated, can be adopted as a point of dogma instead of technique.

Julien joined a team which bragged about their use of DDD. Everything they did followed DDD best practices, they said. The fact that they piled up all sorts of related buzzwords when talking about it should have been a red flag.

Here's one of their "domain" classes:

namespace Acme\Documents\Domain;

interface CakeSessionRepositoryInterface
{
   public function isLoggedIn(string $cookieId);
}

In "domain" patterns, a "repository" interacts with domain objects in your data store. Things it shouldn't do:

  • perform an authentication check
  • interact with cookies
  • care about session information
  • be tightly coupled with your underlying web framework (CakePHP, in this case)

Excluding the curly-brackets, every line in this short snippet is wrong, which is impressive.

It looks like their domain shouldn't drink and drive.

[Advertisement] Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready.Learn more.

CodeSOD: Check and Check

8 June 2026 at 06:30

Today's anonymous submitter sends us a React view that presents some admin options. Of course, it should only show us those admin options if the user is authorized to do that. So let's see how they implemented it:

{(isAdmin || canSeeResults) && (
    <div>
        <p>Admin Actions</p>
            {(isAdmin || canSeeResults) && (
                <div>
                    <button> Show Results </button>
                </div>
            )}
    </div>
)}

If they're an admin or can see the results, we print out an Admin Actions header, and then if they're an admin or can see the results, we show them a Show Results button.

I once had a math teacher who claimed he didn't trust anyone, and that's why he always wore suspenders and a belt. I don't think he's still alive, let alone writing React code, but I see a "belts and braces" approach in play. Though in this case, I don't think it adds any safety.

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

CodeSOD: Blocked the Date

2 June 2026 at 06:30

Volodya sends us some bad date handling code in PHP. Which, I know, you're just reaching for the close tab and yawning when you hear that. You've seen it before. But bear with me, this one still has some fun bits to it.

$monthes = array(
        1 => 'Января', 2 => 'Февраля', 3 => 'Марта', 4 => 'Апреля',
        5 => 'Мая', 6 => 'Июня', 7 => 'Июля', 8 => 'Августа',
        9 => 'Сентября', 10 => 'Октября', 11 => 'Ноября', 12 => 'Декабря'
);

This creates a list of months.

if ( $team->have_posts() ) :
    // Start the Loop.
    while ( $team->have_posts() ) : $team->the_post();

Today, I have learned something about PHP. PHP has an alternate syntax for blocks. Instead of if { statements }, you can do: if : statements endif. Just one more quirk of PHP to make the language more confusing.

This block checks have_posts in an if, and then checks it again in a while, meaning we don't need the if at all, but so it goes. We haven't gotten to the date handling yet, so let's look at that.

        $date = get_the_date();
        $d1 = explode(".", $date);

        if ($d1[1][0]=='0')
            $m = $d1[1][1];
        else
            $m = $d1[1][0];
        ?><div class="date"><?php echo $d1[0]." ".$monthes[$m]." ".$d1[2]; ?></div>

We get the date as a string, and then split it out into date parts. This is, of course, highly locale specific, but clearly they know what locale they're in. Then they look at the array of date parts. The second element holds their "month" string, as two digits, so they look at the digits. If the month string starts with a 0, they grab the second character and put it in $m. Otherwise, they grab the first character and put it in $m. Then they use $m to look up the $monthes.

Unless there's some substring weirdness going on that I don't know about, this code… doesn't work? Right? Since they're grabbing only a single character out of $d1[1] every time, for months later in the year, $m is only ever going to hold 1, and thus we only output Января, meaning we get four months of January, which just seems cruel, honestly, at least in the Northern Hemisphere.

As with all bad date handling code, this could easily be fixed by just using the built in functions, even in PHP. What I'm going to take away from this though is that PHP's syntax lets you write in Visual Basic or Ruby if you're determined enough. And you can mix and match, so enjoy a codebase that has :/endif and {} scattered throughout.

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

Let's Be Facebook!

1 June 2026 at 06:30

The real WTF is that our long-time friend and submitter Argle failed to dissuade all three of his sons from pursuing IT careers of their own:

Back circa 2012, my three sons all got jobs at a company that had a brilliant web project. So brilliant that it had the support of a Disney VP, the mayor of the city, and other VIPs. At one point, my sons asked to borrow money to invest in the project. They are good boys (one is now a senior developer with Proctor & Gamble), so I backed them.

A year later, the project was released late, over budget, and not fully functional.

Facebook dislike

My boys convinced the CEO to bring me in to fix things. I fixed things. In that time, I found out they had taken bids on the project. Bids were nominally $15,000, some higher, some lower, of course. All but one group that had bid $5,000. Their plan? Hire some programmers in India for $8/hour and pocket the money without having to do work themselves.

Costs had shot well over $35,000 before I was brought in.

After I got the system working, I went to one of the weekly general standups for the company. The CEO walked in and said something like, "I just learned that Facebook was written in PHP. I think we should rewrite the whole project in PHP. That's what we really need to do."

And thus the decision was made.

A meeting was held the next day to discuss how long it would take to remake the project in PHP instead of C#. Bear in mind, a year and a half had been thrown into making the project thus far.

Going around the table, everyone said between 2 and 3 weeks. There was one other programmer in the company who had exactly 2 months of work experience; he simply parroted what the others had said before him. There was also the general contractor who leased the building to the company. He was involved with the project, and was second-to-last to speak. I fully expected this contractor to have more sense. He came in at 3 to 4 weeks.

My mouth dropped open.

It was my turn. You know those psych tests where you get someone who acts sensibly when alone, but conforms with the rest of the crowd when there's more than one? I'm simply not that guy. I said, "Those are absurd estimates! This will take a minimum of 5 months before it's in beta stages and not ready for public consumption for another couple more months."

The next day, I got a call telling me my services were no longer needed because "I wasn't forward-thinking enough for the company."

My boys stayed on another year, so I got regular reports on the "upgrade." Sure enough, just shy of 8 months later, the new system went live.

As they say, the most experienced person will be the one to accurately tell everyone that it will take longer and cost more than everyone else says.

Anyone else have their own intergenerational WTFs? Please share in the comments!

[Advertisement] Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready.Learn more.

Error'd: Super SEO Strategies

29 May 2026 at 06:30

It's ironic -- this site gets absolutely inundated with blogspam from people trying to improve their SEO ranking, and yet the only requirement to get your website linked is one dumb little typo in the right menu.

Faithful Michael R. is still job hunting, now even farther afield. "I shall try the gigs in United Kingsom. https://electronicmusicopenmic.com/"

43d63150fa7d48d3a7998e14e111c211

B.J.H. is getting hot undeh the collah. "Weather.com is an endless source of WTF. Today the high temperature will be 53F, unless you care about any hour after 8:00 AM. (And why don't they have enough room to spell out "hour"?)"

561594f875db486085450afbb4f65a4e

Jake W. isn't storming about like BJ. He just wants us to know there's an opening at Durmstrang. No stress.

8eefa2a1182146b3b595a3fbbfef5012

Martin K. reveals "The resignation of the Microsoft Denmark CEO broke more than news, it also broke the date."

73c8b26e71ed4518bbdfeacc9850629f

"confirmation.message.text" incoming from Totty "Snarky comment. Snarky comment. Snarky comment."

d0e6feb93e324e509946643027ddbc5e

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

CodeSOD: What Condition is This

28 May 2026 at 06:30

Untodesu sends us this submission, with this comment:

Literally no idea what kind of drugs the guy was taking but nonetheless we've rewritten it to be just a two-liner

Well, that doesn't tell us a lot about what to expect from the code, but let's take a look.

QStringList TableViewAssembly::parametersFilter(ProbePart::Type type, int pos, QList<ProbePart> probeDesign) {
    QString to, from;

    if(pos == -1) {
        if(probeDesign.length() == 0) {
            to = "*";
            from = "AutoJoint";
        } else {
            to = probeDesign.at(0).fromMounting();;
            from = "AutoJoint";
        }
    } else if(pos == 0) {
        if(probeDesign.length() == 1) {
            if(probeDesign.at(pos).type() == ProbePart::Type::Stylus) {
                to = probeDesign.at(pos).fromMounting();
                from = "*";
            } else {
                to = "*";
                from = probeDesign.at(pos).toMounting();
            }
        } else {
            to = probeDesign.at(pos + 1).fromMounting();
            from = probeDesign.at(pos).toMounting();
        }
    } else if(pos == probeDesign.length() - 1) {
        if(probeDesign.at(pos).type() == ProbePart::Type::Stylus) {
            if(probeDesign.length() <= 1) {
                from = "*";
                to = probeDesign.at(pos).fromMounting();
            } else {
                from = probeDesign.at(pos - 1).toMounting();
                to = probeDesign.at(pos).fromMounting();
            }
        } else {
            from = probeDesign.at(pos).toMounting();
            to = "*";
        }
    } else {
        from = probeDesign.at(pos).toMounting();
        to = probeDesign.at(pos + 1).fromMounting();
    }

    return { to, from };
}

QStringList andQList tell me that this is a Qt-based application. The goal of this function seems to be to take some inputs about a "probe part" and construct a pair of strings. Let's trace through it.

Let's just walk through the conditions, quickly, without worrying too much about the inside. We look at pos, and check for three cases: either pos is -1, 0, or probeDesign.length() - 1.

Inside each of those branches, we also check the length of the list, testing if it contains no elements, exactly one elemnet, or more than one element. We also check if the part in question is a stylus.

With that in mind, let's see if we can summarize the conditions here. If pos == -1, we do some automatic stuff, using the first element in the list if there is one. If pos == 0 and there's exactly one element in the list, we grab the first element and link it to * (the to/from order depends on the stylus question). If there's more that one element in the list, we pair the current pos with pos+1; notably, in this branch, pos is definitely zero. If pos is the last element in the list, we follow the same logic, but pair with pos-1, with a side branch for checking against the length of the list.

It's all bounds checking. That's all this code is. Bounds checking that's gotten out of hand. The main branch here is actually the final else: that's where most of the code is going to pass through. All the other branches are just handling edge cases. Literal edge cases, as in "the edge of the list".

Untodesu didn't supply the two line version, but based on the fact such a version exists, I also suspect that many of these branches weren't actually used. Or, at least, based on the actual business rules, could be combined.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

CodeSOD: Are There Files Yet?

27 May 2026 at 06:30

Are there any files to send? That's the question that Chris C's predecessor had. So they asked it. Again. And again. And again.

Chris writes:

I'm occasionally called upon to troubleshoot an ecommerce application that was built in the PHP 5.x days and has been running largely untroubled by maintenance or modernity (aside from the backported security patches to its binaries) ever since.

if(sizeof($files) > 0){
		if(sizeof($files) > 0){
				foreach($files as $file){
						$mime->addAttachment($file);
		}
		}
}

Indentation as per the original.

If the files array contains items, then if the files array contains items, then we iterate across the files array, which hopefully contains items, and add them as an attachment to an email.

I feel like the way this got indented, the developer responsible knew, deep down, that this was wrong. They lacked the reading comprehension to understand why, but deep down in their spleen, something was screaming at them. And thus those stacked curly brackets at the end there.

Of course, none of the conditionals are needed: a foreach on an empty object just does nothing.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

Whales Ahoy!

26 May 2026 at 06:30

The waters are even more dangerous than we imagined. Have a look at some of the crazed whales our brave submitters and commenters have encountered in the wild.

First comes an Anonymous tale of woe:

Killer whales (Orcinus orca) spyhopping to locate a crabeater seal (Lobodon carcinophaga) on an ice floe in Antarctica.

Our company makes apps for businesses. We have 1 MAIN client whose CEO can make or break our company, and his wish is our command. He sent a priority email on a Friday night saying the app was slow and needed to be fixed.

The client CEO is so important that he works directly with our CEO, who decided to PM this huge issue.

All weekend, we were trying out tons of different things to optimize this "slow" app that "wasn't loading or refreshing." We deployed the app Monday night after a weekend of unpaid overtime (darn salary). On Tuesday, the account manager made a bug card to officially represent the work we did, and they posted a previously-unseen video of the slowness.

There is a refresh icon that spins when clicked. The video was of the refresh icon, and it was spinning for an extra second after the data loaded (and jumping 2 pixels from padding styling).

That is what was high priority.

I mean, we all hate the system, but sometimes the system is actually there to protect us.

Next, we have Daniel's ongoing peril:

We do digital flyers/circulars/ads. Eight years ago, that meant we got PDFs from retailers and turned them into digital content. One huge retailer (hundreds of stores) wanted a dynamically-created flyer that would have up-to-date pricing twice a day. We didn't have time to build out a full digital solution (which would have made sense), so instead we spent six months banging together a solution with spit and duct tape which baked out hundreds of PDFs every morning and afternoon. This one retailer was responsible for about 40% of our processing power.

We're finally getting somewhat closer to phasing this out, but "it worked" for this long ...

Finally, let's be grateful Brian escaped with his life!

Worked for a company that was building a component of a high-profile weapons platform for one of the major military suppliers. We had taken over the project from another company that was under-performing, so we were already behind schedule from the minute the contract was signed. Of course this company saw fit to treat us more as a subsidiary than a subcontractor. Including, for a time, sending one of their own managers to sit in our lab and observe (read: babysit) us. On Saturdays. Then they demanded we start working shifts to make more use of the lab equipment, and I got the bad draw: 3 AM - noon. Never mind that I had just gotten married (they actually called to tell me this while I was on vacation the week after my wedding) and would like to actually spend some time with my wife ...

That experience soured me on the whole military-industrial complex for a long time. To this day I still get headhunters pinging me to work for that megacorp; I just chuckle and delete their messages.

Have these tales knocked loose any foul memories that your brain tried to repress? Send them to us!

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

CodeSOD: Classic WTF: One-and-a-Half-Tiered Application Design

25 May 2026 at 06:30
It's a holiday in the US today, so we're reaching back into the archives. What we really need is a single function that can do it all, and by "it" we mean "ruin your life." Original --Remy

There are several types of bad code; there's lazy code, frantic code, unaware-of-a-better-way code, and aware-of-a-better-way-but-too-apathetic-to-do-it code, to name a few. Then there're amalgamations of different types of bad code.

Môshe encountered such an amalgam when his company was trying out a new delivery service. Môshe spent some time evaluating the IE-only web interface, and was curious about some JavaScript errors he was getting. Strangely, he noticed variables named dateSQL, newSQLTag, and modeSQL.

Môshe dug a little deeper, probably thinking that his suspicions couldn't possibly be correct, only to find sendLinkVal() in the page's code:

function sendLinkVal(theDate,theStatus,MainTitle,PageTitle){
  var dateSQL = " AND J.JBDeliveryDate=''" + theDate + 
    "''"
  var status = ""
  var newSQLTag =""
  var PageTitle = PageTitle
  var MainTitle = MainTitle
    //alert(dateSQL)
      switch (theStatus){
        case "Confirmed":
          dateSQL= "" 
          var modeSQL = ""
          modeSQL = " AND (J.JBCompanyID=31337) "
          status = " GlobalJobStatusView AS J WHERE J.JBCollectDate=''
	    " + theDate + "'' AND J.JBConfirmed=''Yes'' AND 
	    J.MIStatusCode<>5" + modeSQL + " AND 
	    (ISNULL(J.JBCancelled, 0) <> 1) ORDER BY 
	    Convert(int, J.MIJobID)"
        break;
        case "Unconfirmed": 
          dateSQL= ""
          var modeSQL = ""
          modeSQL = " AND (J.JBCompanyID=31337) " 
          status = " GlobalJobStatusView AS J WHERE J.JBCollectDate=''
	    " + theDate + "'' AND J.JBConfirmed=''No''" + 
	    modeSQL + " ORDER BY Convert(int, J.MIJobID)"
        break;
        case "Complete":
          dateSQL= ""
          var modeSQL = ""
          modeSQL = " AND (J.JBCompanyID=31337) " 
          status = " GlobalJobStatusView AS J WHERE J.JBCollectDate=''
	    " + theDate + "'' AND J.MIStatusCode=5" + 
	    modeSQL + " ORDER BY Convert(int, J.MIJobID)"
        break;
        case "Unconformed": 
          dateSQL= ""
          var modeSQL = ""
          modeSQL = " AND (J.JBCompanyID=31337) " 
          status = " GlobalJobStatusView AS J WHERE J.JBCollectDate=''
	    " + theDate + "'' AND (J.MIConformance IS NOT NULL 
	    AND J.MIConformance<>'''') " + modeSQL + " 
	    ORDER BY Convert(int, J.MIJobID)"
        break;
        case "NoDelDate":
          dateSQL= ""
          var modeSQL = ""
          modeSQL = " AND (J.JBCompanyID=31337) " 
          dateSQL =" GlobalJobStatusView AS J WHERE J.JBDeliveryDate 
	    IS NULL " + modeSQL + " ORDER BY Convert(int, J.MIJobID)
	    "
        break;
        case "Collections":
          // the dateSQL is not required so set it to nothing so that it 
          // doesn't interfere with the sql being generated at the end of 
          // the function.
          dateSQL= "" 
          var modeSQL = ""
          modeSQL = " AND (J.JBCompanyID=31337) "
          status = " GlobalJobStatusView AS J WHERE J.JBCollectDate=''
	    " + theDate + "''" + modeSQL + " ORDER BY 
	    Convert(int, J.MIJobID)"
        break;
        case "Deliveries":
          // the dateSQL is not required so set it to nothing so that it 
          // doesn't interfere with the sql being generated at the end of 
          // the function.
          dateSQL= "" 
          var modeSQL = ""
          modeSQL = " AND (J.JBCompanyID=31337) "
          status = " GlobalJobStatusView AS J WHERE J.JBDeliveryDate=''
	    " + theDate + "''" + modeSQL + " ORDER BY 
	    Convert(int, J.MIJobID)"
        break;
        case "ColAndDel":
          // the dateSQL is not required so set it to nothing so that it 
          // doesn't interfere with the sql being generated at the end of 
          // the function.
          dateSQL= "" 
          var modeSQL = ""
          modeSQL = " AND (J.JBCompanyID=31337) "
          status = " GlobalJobStatusView AS J WHERE ((J.JBDeliveryDate=''
	    " + theDate + "'') OR (J.JBCollectDate=''" + 
	    theDate + "''))" + modeSQL + " ORDER BY 
	    Convert(int, J.MIJobID)"
        break;
        case "Subcontractor":
          // the dateSQL is not required so set it to nothing so that it 
          // doesn't interfere with the sql being generated at the end of 
          // the function.
          dateSQL= "" 
          var modeSQL = ""
          modeSQL = " AND (J.JBCompanyID=31337) "
          status = " JobAndLoadView AS J WHERE (J.JBDeliveryDate=''
	    " + theDate + "'') " + modeSQL + " 
	    ORDER BY Convert(int, J.MIJobID)"
        break;
        case "Cancelled":
          // the dateSQL is not required so set it to nothing so that it 
          // doesn't interfere with the sql being generated at the end of 
          // the function.
          dateSQL= "" 
          var modeSQL = ""
          modeSQL = " AND (J.JBCompanyID=31337) "
          status = " GlobalJobStatusView AS J WHERE (J.JBCollectDate==''
	    " + theDate + "'') " + modeSQL + " AND 
	    ISNULL(J.JBCancelled, 0) = 1 ORDER BY Convert(int, J.MIJobID)"
        break;
        default : status ="";
      }
        newSQLTag = dateSQL + status;
        document.all.hiddenForm.linkVal.value = newSQLTag;
        document.all.hiddenForm.PageTitle.value = PageTitle
        document.all.hiddenForm.MainTitle.value = MainTitle
        document.all.hiddenForm.submit();  
    //alert(newSQLTag)
  }

Môshe could replace his customer ID with any other and access customer data, and for that matter, to modify or delete whatever he wanted. He could add or remove columns to tables. He could possibly even change permissions, add his own database user and deny all other users access.

Shocked, Môshe called the delivery service, who got him in touch with the developer of the system. This developer was equally shocked to learn that it was even possible to view a web page's JavaScript code, let alone that his architecture was open to SQL injection attacks from virtually any angle. He took immediate and decisive action; all queries were moved to the .NET backend.

Of course, the queries still didn't use parameters and are therefore still open to SQL injection, but now it takes slightly more effort to hack.

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

Error'd: April is Special, and so are you

22 May 2026 at 06:30

"April is special," writes Elwin. It is, but take heart May, every month is special at TDWTF.

ef33dacc82c1495bbc2c68cf30461f3c

"Admiral Ackbar is pinterested," punned The Beast in Black

0b5ff0ba77cc480cb3c0a6ca91ef10b6

Manuel H. clocked something off on this website. "Noon seems to be very late in Lithuania, or maybe only in this hotel restaurant in Vilnius." 15H AM must be on some planet with a 32H day.

18d8b28ac37243708f1f4711be97cebf

"Amazon can't make up its mind!" ranted an anon. "Do I need to wait 2 business days or 3? Make up your mind Amazon!"

abc72aa0987b4e84816906e2b598dc11

Duston decided to close us out with a pun. "Looks like they have a problem, but it's trivial." Well done.

a821a18e000c4152a327d79dd2a05744

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

CodeSOD: In the Know

21 May 2026 at 06:30

Delilah works in a Python shop. Despite Python's "batteries included" design, that doesn't stop people from trying to make their own batteries from potatoes. For example, her co-worker wrote this function:

def key_exists(element, key):
    if isinstance(element, dict):
        try:
            element = element[key]
        except KeyError:
            return False
        return True

Python, of course, has an in operator. key in dictionary is an extremely common idiom. There's no reason to implement your own. Certainly, there's no reason to re-implement it by catching and throwing exceptions.

This is ugly, stupid, and bad. It gets worse, though, when you see how it gets used.

for key in old_yaml_data:
    if key in new_yaml_data:
        if old_yaml_data[key] != new_yaml_data[key]:
            temp = new_yaml_data[key]
            new_yaml_data[key] = merge(new_yaml_data[key], old_yaml_data[key])

            if key_exists(new_yaml_data[key], 'image') and key_exists(old_yaml_data[key], 'image'):
                new_yaml_data[key]['image'] = temp['image']
            elif key == "databases":
                revert_db_tags(new_yaml_data[key], temp)

This code is attempting to upgrade "old" YAML data with "new" data. So it's basically merging dictionaries, which is a great case for the in operator.

And they use the correct idiom on the second line there! This was written by one developer! They do the standard key in new_yaml_data check. And they also use key_exists. I can only assume that they had a stroke between starting and finishing this script, which I'll note is, in total, 48 lines long.

Here's the whole short script, which is just generally a mess. Slapped together Python code that's trying to be a "smarter" shell script, but is definitely written with the elegance of hacked-together-bash.

import sys
import yaml
from jsonmerge import merge

appHomePath = sys.argv[1]
oldValuesYAML = appHomePath + "values.yaml"
newValuesYAML = appHomePath + "/upgrade_version/values.yaml"
with open(newValuesYAML, 'r') as f:
    new_yaml_data = yaml.load(f, Loader=yaml.loader.FullLoader)
with open(oldValuesYAML, 'r') as f:
    old_yaml_data = yaml.load(f, Loader=yaml.loader.FullLoader)
def key_exists(element, key):
    if isinstance(element, dict):
        try:
            element = element[key]
        except KeyError:
            return False
        return True

def revert_db_tags(old_yaml_data, new_yaml_data):
    dbList = ["mongoDB", "postgresDB"]
    mongoDbTagsToRevert = ["mongoRestore"]
    mongodbKeysToDelete = []
    postgresDbTagsToRevert = []


    for db in dbList:
        old_yaml_data[db]['image'] = new_yaml_data[db]['image']
    for mongoDbTag in mongoDbTagsToRevert:
        old_yaml_data['mongoDB'][mongoDbTag]['image'] = new_yaml_data['mongoDB'][mongoDbTag]['image']
    for mongoDbTag in mongoKeysToDelete:
        del old_yaml_data['mongoDB'][mongoDbTag]

    for postgresDbTag in postgresDbTagsToRevert:
        old_yaml_data['postgresDB'][postgresDbTag]['image'] = new_yaml_data['postgresDB'][postgresDbTag]['image']

for key in old_yaml_data:
    if key in new_yaml_data:
        if old_yaml_data[key] != new_yaml_data[key]:
            temp = new_yaml_data[key]
            new_yaml_data[key] = merge(new_yaml_data[key], old_yaml_data[key])

            if key_exists(new_yaml_data[key], 'image') and key_exists(old_yaml_data[key], 'image'):
                new_yaml_data[key]['image'] = temp['image']
            elif key == "databases":
                revert_db_tags(new_yaml_data[key], temp)

with open(newValuesYAML, 'w') as f:
    data = yaml.dump(new_yaml_data, f, sort_keys=False)
[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

CodeSOD: Find a Bar for This One

20 May 2026 at 06:30

A depressing quantity of software is what I would call a "data pump". I have some data over here, and I need it over there. Maybe I'm integrating into a legacy app. Or into an ERP. Or into a 3rd party API. At the end of the day, I have data in one place, and I want it in another place.

Sally has a Java application written in the Quarkus framework, which has a nightly batch that works to keep a table of Bar entities in sync with a table of Foo entities. (This anonymization comes from Sally) These exist in the same database. There is also a Bar webservice, which provides information about the Bar entities. The workflow, such as it is, is that the software needs to find all of the Foo entities that do not currently have associated Bar entities, and then call the Bar webservice to get the required information to create those Bar entities.

Let's see how that works.

@Inject UserTransaction transaction
// If this is annotated with @Transaction the usage in the Message function down below will have some Thread exception
public List<FooData> getAllFoos() {
  try{
    return fooDataRepository.findAllFoos();
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}

We'll worry about that comment in a second, but this function returns a list of all of the Foo objects in the database. It does not return a list of all the Foo objects without associated Bar entities. It's just the whole giant list of everything. The underlying database is a standard relational database; it'd be trivially easy to write that query, even going through the ORM.

Well, that's bad, but it's all pretty minor. How does the actual update go?

// Can't be annotated with @Transaction because Oracle DB can handle the given Amount of dataEntities in one Transaction '\._./'
Message updateBarsWithFoos() {
  List<FooData> foos = getAllFoos();
  if(!foos.isEmpty()){
    foos.forEach(foo -> {
      try{
        transaction.begin();
        if(barRepository.findByName(foo.getName()) == null){
          if(barDataService.searchByName(foo.getName()) != null && barDataService.searchByName(foo.getName()).marker() != null){
            barRepository.createBar(barDataService.searchByName(foo.getName()));
          }
        }
        transaction.commit();
      } catch (Exception e) {
        try {
          transaction.rollback();
        } catch (Exception ex) {
          throw new RuntimeException(ex);
        }
      }
    });
  }
  return new Message(MessageLevel.INFO, "Created bars")
};

Ah, the real WTF is that it's an Oracle database. That's always a WTF.

But let's trace through this code.

We get all of our Foo entities. We check for emptiness and then do a forEach, which seems to make the empty check superfluous: a forEach on an empty list would be a no-op anyway.

We start a transaction, then check the database: if there are no Bar objects that link to Foo, then we call into the barDataService to find data. If there is, we call into the service again, to see if the marker property is not null. If it is, we call into the service again to get the actual data we're putting into the database. Then we close the transaction. If anything goes wrong, we rollback the transaction and chuck an exception up the chain.

That is three web service calls inside of a database transaction. Three calls which could easily be one, and that call could easily also happen outside of a transaction if you're mindful about confirming your constraints. And of course, because they're not mindful at all, they need to manage the transaction directly, and can't use the @Transaction annotation provided by their framework, which would at least cut down on some of the boilerplate.

Now, I'm sure you'll be shocked - shocked - to learn that the webservice is actually a bit flaky, and thus times out from time to time. And this isn't the only batch job running, which means the long-lived transactions cause all sorts of contention and terrible performance across the various batches. And this app doesn't have its connection pool properly configured, so the entire software stack can exhaust all of its database connections surprisingly quickly, causing yet more failures.

The root of the WTF, of course, is doing this as a batch job. A well engineered application would do everything it could to not create data in the database that isn't referentially sound. There, Sally gives us the one bit of good news:

My current project will do away with the batch processing altogether, so we can say, "RIP, transactional wholesale triple caller!"

[Advertisement] Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready.Learn more.

Three Digit Acronyms

19 May 2026 at 06:30

JB has a database table that, at first glance, looks like one of those data warehouse tables that exists to make queries performant. You know the sort, the table that contains every date between 1979 and 2050, or every number out to 1,000,000 or something. It looks dumb, but it helps make certain joins and queries performant.

The database table is called three_alpha_numerics. It has two columns: digit, which contains three characters, and is_numeric, which is a a single character: 'Y' or 'N'. It looks roughly like this:

+-------+------------+
| digit | is_numeric |
+-------+------------+
| 009   | Y          |
+-------+------------+
| 00A   | N          |
+-------+------------+

So, for example, if you wanted all the possible numeric triples, you could SELECT digit FROM three_alpha_numerics WHERE is_numeric = 'Y', which is obviously the easiest thing one can imagine.

So what is this for? Well, it's used by a stored procedure that generates unique IDs. That stored procedure does a left join against another table to find all the unused digits. And here's the real gotcha: that stored procedure only ever uses the rows where is_numeric is Y, meaning the vast majority of the data in this table is never used.

Unique IDs, of course, are an incredibly difficult task for databases to do, so it absolutely makes sense that we create a system that allows us to only have 1,000 unique IDs. That's more than 640, which should be enough for anyone. Having many thousands of unusable alphanumeric triplets is just the cost we have to pay.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

Representative Line: Dating Backwards

18 May 2026 at 06:30

Another representative line, and this one comes from an Excel spreadsheet. But, per Remy's Law of Requirements gathering ("No matter what the requirements doc says, what your users wanted was Excel"), this one was actually written by a developer. A developer who didn't understand how Excel works, but more important, didn't understand how dates worked either.

This comes from Ulysse J.

=CONCATENER(SI(MOIS($A18)>9;ANNEE($A18)-2000;(ANNEE($A18)-2000)*10);SI(JOUR($A18)>9;MOIS($A18);MOIS($A18)*10);JOUR($A18))

Now, the first thing: Excel function names are locale specific. This was written in France, so the functions are French. CONCATENER is "concatenate", SI is "if", MOIS is "month", and so on.

The purpose of this function is to convert a field (cell A18) in DD/MM/YYYY into YYMMDD. So how does it do this?

Well, we check the month. If it's greater than 9, we output the year minus 2000. If it's less than 9, then, we output the year minus 2000, multiplied by 10. That is to say, August, 2026 would start by outputting 260. We repeat this logic for the days: if the day is larger than 9, we output the month, otherwise we output the month times 10. Finally, we output the day.

This is attempting to do padding. There's just a problem. Imagine February 1st, 2009- an actual date in the document. We convert the year into 90, the month into 20, rendering the date as 90210. That's incorrect. And once we get to 2100, if there is still an Excel in 2100 (I joke: of course Excel will still exist in 2100. Humanity won't, but the robots will use Excel), this will also break. Not that it matters- I mean, YYMMDD doesn't make sense by that point.

Obviously, the correct solution is to use Excel's rich, built-in formatting functions to convert between date formats. It's easy! But Ulysse raises another point:

Extra points: even if you do not know how to do proper [formatting], the input format is guaranteed to have correct padding. I would just concatenate parts of it (treating dates as text is bad, but still less bad than treating them as integer triplets).

I will say this: I know a software developer wrote this, because your average Excel user could easily write bad formulas, but never bad in this kind of convoluted way. You need a real expert to do something this bad.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

Error'd: Balmenach Bad Gateway Single Malt

15 May 2026 at 06:30

"Winner ad placement!" snarked our Peter G.

cc79d61a927848f48b2a41988ebf8c5d

Errors on this website are always a shoo-in for the weekly column. An anonymous reader wrote "I got error 500 when I tried to submit an Error'd. Please make the file uploader check if the attached file is within the file upload limit, which I think is less than 4 MB." They shared an audio error'd which may be coming along next week.

fec797b9fc3642a5b940675292dc764e

"Give us feedback - wait, did it work at all?" confused poor I_Absolutely_Want_To_Give F. "As every good service management company, ServiceNow wants feedback, above all."

3d6b7629ef5a4c90bafa3f2e6a21f663

"0 minutes does not equal 0 seconds..." sagely summarized Daniel D. "Claude like floors. I mean floor. But maybe ceil would be better applicable to this calculation, right?"

76dd1834b8394e5ebca32229fa87fb7e

Finally, this one is a real novelty, from Adam R. Is the label actually 27 years old? It certainly could be; Error 502 is a good bit older. But I think this would be our oldest Error'd yet. Adam explained: "This appears to be a real auction for a whiskey bottle whose label does, in fact, say Error 502 Bad Gateway on it. The winning bid: £130. Source: https://www.scotchwhiskyauctions.com/auctions/228-the-179th-auction/876095-balmenach-1998-27-year-old-error-502-bad-gateway-thompson-bros/"

1f77b40a37f24eabbac33a8de3aee9a7

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

The Pride Goeth

14 May 2026 at 06:30

Janči, a master's student of bioinformatics, was seated near the back of a large classroom. This was a simple compulsory elective course geared toward biologists. The professor was currently walking the class through their latest assignment. "We'll need to connect to some Linux servers," he announced.

The other students seated nearby traded blank stares. They were all Mac and Windows users with no IT background. Meanwhile Janči, a veteran Linux user, started feeling a little smug. An easy A was at hand.

Roman key (FindID 519853)

"First," the professor continued, "you'll need a private key."

After the professor had explained a few details, the first WTF came in the form of a bulk email sent to the entire class. The private key was attached. The username was the email address it was sent to.

What do you call the exact opposite of a private key? Janči wondered, bemused.

"You'll also need to download an application to help you log in," the professor said. "I recommend MobaXterm."

As he detailed the process of visiting the SSH client website to download the software, Janči tuned out. He didn't need such hand-holding. He accessed OpenSSH, tried connecting ...

... and failed.

Meanwhile, everyone around him was logging in no problem.

Janči's face burned with embarrassment at this second WTF. His first instinct was to blame the deprecated cryptography of the server. He spent most of the remaining lecture time searching for a way to allow his SSH to use SSH-DSS. (It turned out to be supported the whole time, despite the warnings he received.)

Janči then tried to re-download the "private" key and adjust the SSH config file several times. He cycled through different possible usernames associated with his university email account.

No dice.

He was the only person in the class who hadn't yet logged into the server. Not even the professor was able to help him, since he was using Linux.

Embarrassment and frustration mounted. An hour later, out of ideas, Janči fell back to downloading MobaXterm and running it inside Wine.

It didn't work.

The professor offered him a spare Windows box. "Here, try this one."

Janči booted it up, copied the "private" key to the new machine ... and still couldn't sign in.

Now, this was getting suspicious.

The lecture ended. A friend of Janči's hung back while the rest of the students filed out. "Why don't you try logging in with my credentials instead of yours?" she asked.

Janči was up for anything at that point.

It worked. On his own machine, on the Windows box, everywhere.

With that lead in mind, Janči opened the server's /etc/passwd file to look at all the usernames. He noticed that, unlike everyone else, his username and email address didn't match.

His university used Microsoft emails. Everyone had several address aliases, and they could also use whatever email address they liked in the system, even a personal one.

Janči had chosen to use a school email in the form of <number>@uni.uni. Unfortunately, the Ubuntu server didn't like the idea of user being named just <number>, so it had renamed it to user<number>. Some script for generating SSH configuration had probably failed from there, because Janči also discovered that his user home directory was missing a .ssh directory and known_hosts file.

Unfortunately, due to restricted access, he wasn't able to copy them from any of his classmates. In the end, he could connect to the server as any of his classmates, but not as himself.

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

CodeSOD: Over and Under Reaction

13 May 2026 at 06:30

Today's anonymous submitter sends us two blocks. The first is a perfectly normal line of React code:

const [width, setWidth] = useState(false)

This creates a width variable, defaulting it to false, and a setWidth function, which lets React detect when you change the variable, and trigger a re-render. Importantly, this mutation only happens on the next render, which means if you call setWidth and then check width, you won't see your change happen.

As I said, this is perfectly normal React code. Well, almost. First, I have to ask: why on Earth is width being set to a boolean value? "How wide are you?" "Yes." It's possible that there's a good reason for this, though I suspect that it's unlikely.

The second issue, however, is that the linter complained that the setter was never actually used. That was odd, because if our submitter grepped the codebase, there were two calls to setWidth. Let's see what that looked like:

const show = (show) => {
    setWidth(show)
    setWidth(!show)
}

We create a function show, where we expect a boolean value, and then we setWidth with that value, and then with the negation of that value. So show(true) will set width to be false. To make matters more confusing, we set width both ways, and I assume this is someone trying to get around React's state management. React won't trigger a re-render if you set the state to a value it already has. So I suspect they're twiddling to try and force it to re-render, and I also suspect that this might not work? Even if it does, this isn't how you should be using React. As I said, I'm no React expert, but as the saying goes: "I don't have to be a helicopter pilot to know that when I see a helicopter hanging upside down from a tree someone messed up."

Our submitter writes:

Got hired to cleanup a mission critical website for a company that had just learned that offshore teams might not be worth the cost saving measures.

"Pay me now or pay me later."

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

CodeSOD: Please Find, Rewind

6 May 2026 at 06:30

As previously discussed, C++ took a surprisingly long time to get a "starts with" function for strings. It took even longer to get a function called "contains". In part, that's simply because string::find solves that problem.

Nancy sends us a… different approach to solving this problem.

bool substringInString(string str, string::iterator &it)
{
  string tmp;
  bool result = false;
  int size = str.length();

  int count = 0;
  while (count < size)
  {
    tmp += *it;
    it++;
    count++;
    if (tmp.find(str) != string::npos)
    {
      result = true;
      it -= size;
      break;
    }
  }

  if ( !result)
  {
    it -= size;
  }

  return result;
}

This function iterates across a string, character by character. In this iteration, we copy one character at a time into tmp. Then we see if tmp contains our search str. If it does, we break out of the loop after rewinding the iterator. Outside of the loop, we check if we found the substring, and if we did, we rewind the iterator. Then we return true or false based on whether on not we found the substring.

So wait a second. str is our search string. it is where we're searching. And we copy from it up to our search string's length into a temporary string. We then do a find in that temporary string- hey! This is just a startsWith check written in the most insane way possible.

Why even bother with the while loop? While tmp is shorter than the search string, the answer is always "no, we haven't found it". And the developers knew that- that's why they always rewind size characters on the iterator. They're always searching exactly that many characters. Of course, since we always rewind the same amount, we can also just move the it -= size statement out of the loop and out of the if statement and do it once.

Nancy calls this "a little gem" in a "large codebase". Yeah, a real gem.

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

CodeSOD: Not for Nullthing

5 May 2026 at 06:30

Today's anonymous submitter sends us some code that just makes your mind go… blank when you look at it.

	public static boolean isNull(String value) {
		return StringUtils.isBlank(value);
	}

StringUtils.isBlank comes from the Apache Commons library. It's a helper function for Java which returns true if a string is, well, blank. "Blank" in this case is: empty, null, or only whitespace. So it's important to note that isBlank may return true on a null, but it isn't truly a null-check, so wrapping it in isNull is just confusing.

But imagine I've got another problem. Let's say I have a database that's been poorly normalized and maintained. And so I have a bunch of fields that maybe are null, but some also maybe contain the string "null". What am I going to do then? I need another function.

	public static boolean isNullAndNull(String value) {
		return isNull(value) && "null".equalsIgnoreCase(value);
	}

Ah yes, isNullAndNull, the clearest and easiest name I could imagine for this. It tells me exactly what the function is checking: is it null, and is it also null? We add a second check to our isNull call- we check if the input value matches the string "null". Except we're &&ing the conditions together. So this function will always return false. It can't both be blank and contain the string "null".

Which means Jennifer Null, who is a real person, can breathe easy. This version of a null check won't think she's nothing.

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

Empty Pockets

4 May 2026 at 06:30

If you've seen one developer recounting how their AI agent deleted production, you've seen them all. They're mostly not interesting stories. It's like watching someone speeding through traffic on a motorcycle without a helmet: the eventual tragedy is sad, but it's unsurprising and not an interesting story to tell. It's not even interesting as a warning: the kind of person who speeds on a motorcycle without a helmet isn't doing so because they don't understand the danger. They've just decided it doesn't apply to them.

But the founder of PocketOS, Jer, recently shared how- whoopsie!- their AI agent deleted production. There's a lot of ingredients that go into this particular disaster, which I think makes it interesting, because the use of a poorly supervised AI agent is only one ingredient in this absolute trainwreck of a story.

PocketOS is a small company that makes software for rental companies to manage reservations. Car rentals are a big customer, but the tool is more general than that. They manage all of their infrastructure via a service called Railway. Railway is a pretty-looking GUI tool for automating your deployments and the target environments.

PocketOS also is heavily adopting Cursor wrapping around the Claude model. They've paid big bucks for the top-end model offered. Many of their components, like Railway, offer MCP services so that their LLM can do useful things. They're using the Claude LLM to automate as much as they can.

So far, this is all a pretty typical setup. They pointed Claude at their code and gave it a "routine" task, and sent it to work. It toddled through the problem and encountered a credential issue. It "decided" that the fix for this issue was to delete a storage volume and recreate it. It scanned through the code to find a file containing an API key, found it, and then sent a POST request via cURL to delete the volume in question.

Jer writes:

To execute the deletion, the agent went looking for an API token. It found one in a file completely unrelated to the task it was working on. That token had been created for one purpose: to add and remove custom domains via the Railway CLI for our services. We had no idea — and Railway's token-creation flow gave us no warning — that the same token had blanket authority across the entire Railway GraphQL API, including destructive operations like volumeDelete. Had we known a CLI token created for routine domain operations could also delete production volumes, we would never have stored it.

Wait, the tokens you create in Railway all have god-level privileges? That sounds like a terrible idea. And you were storing the token in your code? We'll come back to this in a moment, but sure, this is bad, but you can just restore from backup, right?

The volume was deleted. Because Railway stores volume-level backups in the same volume — a fact buried in their own documentation that says "wiping a volume deletes all backups" — those went with it. Our most recent recoverable backup was three months old.

Oh. Oh no.

Now, I don't think it's literally true that Railway is storing your backups literally in the same volume as the thing they're backing up. I certainly hope not. But they do apparently delete your backups when you delete the volume associated with them. Which is a choice, certainly. A bad one. And one that they documented, according to Jer. It was, in his words, "buried" in the docs.

But let's go back to the tokens for a moment. I am not a Railway user, but I checked out the tool and went through the process of creating a project token. And while no, Railway does not give you big red flags warning you "Hey, this token can do ABSOLUTELY ANYTHING", it also never gives you an opportunity to scope the token. Which, I don't know about you, but the first thing I do when I create an authentication entity is try and figure out how to control its authorizations, because I assume at the start it doesn't have any. That'd be sane.

The scoping happens when you create the token, depending on what context you're in when you do it. It's only a handful of scopes, and no fine grained permissions on API keys at all. The lowest level is "Project" which can do anything to a single environment- which does mean that even if you, like Jer's team, wanted to have a script that changed some DNS settings in production, that same key could be used to delete volumes in production. Which means you really really want to take care of that key, and you certainly don't want to leave it where some junior developer or bumbling AI agent can find it.

Jer also complains that Railway shouldn't allow an API call to take destructive actions without more protections, like forcing someone to type in the name of the thing being deleted or sending a confirmation email, or something. This, I'm more skeptical of. Most cloud providers don't offer anything like this in their APIs, at least that I've seen, because on a certain level, if you're invoking the API with the proper credentials, that's a big enough hill to climb that we can assume you've intended your action. The correct way to protect against this is properly scoped keys and keeping those keys secure and not just lying around in plain text. There's a certain aspect of understanding that you're using a potentially dangerous tool and need to take the responsibility for safety into your own hands; while a table saw can easily take some fingers off, it's perfectly safe when used correctly.

This is all bad, but how can we make it worse? Well, Jer demanded that Claude "explain itself". In a section called "The Agent's Confession", Jer highlights that the agent is able to identify the explict rules that it failed to follow.

Read that again. The agent itself enumerates the safety rules it was given and admits to violating every one. This is not me speculating about agent failure modes. This is the agent on the record, in writing.

No, it is not the agent on record. I see this kind of thing a lot when people talk about LLMs. An LLM cannot explain its reasoning. It cannot go on "the record". It cannot confess to anything. While what it plops out when asked might be interesting, it is not an explanation. The only explanation is that it's a powerful statistical model trying to create a plausible string of tokens! It's simply looking at its context window and your prompt and trying to predict what it should say. It can tell you what rules it violated not because it understands the rules or knows it violated any rules, but because those rules are in its context window. If you ask it right, it'll confess to killing JFK and framing Oswald for the crime.

Jer then tries to ensure that Cursor takes some of the blame, pointing to Cursor's "guardrails" documentation. Except, here, the documentation is actually quite explicit about what those guardrails guarantee. If you're using a first-party tool, it will prohibit unsafe operations. When using 3rd party MCPs, like Railway's, the only guardrail is that it requires human approval for every action- unless you update your allowlist for that MCP. If you put them in your allowlist, the guardrails go away. Jer argues that tools should enforce more protection against LLM behaviors, but the problem with that is people- like the PocketOS team- turn those protections off. And like a lot of safety mistakes, they can get away with it all the way up until the point where they can't.

Jer follows this by listing off a pile of other times using Cursor has caused disasters, which isn't making the argument he thinks it is: yes, Cursor is dangerous, but those dangers are well known. It makes the choice to turn Cursor loose without strict supervision seem even more foolish.

Jer writes:

For now I want this incident understood on its own terms: as a Cursor failure, a Railway failure, and a backup-architecture failure that all happened to one company in one Friday afternoon.

It's also a PocketOS failure. It's a failure to properly assess the tools and environments you chose to use for your product. A failure to read and understand the docs for vital features, like *backups*. A failure to employ even the most basic safeguards. A failure to put a second's thought into key management- even if that key was only for DNS entries, you still shouldn't chuck it in source control. A failure to have a competent backup strategy. It's worth noting that they did restore from a three month old backup, which means they were at one point taking backups outside of Railway's volume setup. That was a wise decision. That they stopped is a failure.

The first rule of disaster retrospectives is that it's never one piece that's the failure. It's never one person's fault, one tool's fault, one vendor's fault. It's a systemic failure. Railway's keys should be finer grained. But also, you shouldn't leave keys lying around. Deleting backups when you delete the volume is a terrible idea, but having only one service for backups (that's also your primary site) is a terrible idea. Claude's ability to enforce its own guardrails should be better, but LLMs are notoriously dangerous about this: you should know better, and by your own words you did.

This is not an anti-AI post, or even a "get a load of this asshole" post. It is a "understand the damn tools you're using" post. Be critical of them. Don't trust them. Ever. Especially LLMs, because the worst part of an LLM is that it takes away the one thing computers used to be good at: predictable, deterministic behavior. But not just LLMs: don't trust your cloud provider, don't trust your infrastructure manager. Dig into them and understand how they work, and if they seem to complicated to understand, than they may be too complicated to trust.

Update: As pointed out in the featured comment below, Railway did finally get a backup restored. So they got their data back. Yay? From the post, Jer remains committed to making this a Railway issue and not a PocketOS issue.

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

Error'd: Parametric Projection

1 May 2026 at 06:30
Roger C. gets on second base with an unforced error. "Not only is the content too large, the error message informing us of this is also too large to fit the visible space. A layered, double WTF."

782b1790d9d549d6a8acf4045669d7a6

"AWS Spellcheck Fail!" alerts Peter "If only someone at AWS knew the correct paramters to activate the spellcheck."

ee85e87fd7cb4cc2ac3038cb9f97ccf8

"How long is too long for a job to be open? " wonders Lincoln K. "I didn't even know LinkedIn existed 61 years ago, let alone was accepting postings... Though only 81 applicants in that time is hardly an impressive turn-out." For a "Vice President Operations and Quality Control", no less.

1c3d4b06a37e4119b62dc39bad29b9a3

An anonymous Richard reports "This came through my door. On a card that, in order to get to my door, had my full address printed on it, including my ."

9df5e07d210846f08dc925105f19b64b

Oenophile Abroad Michael R. shares "My Macbook broke after being "exposed" to red wine. As a German in London it pleases me so see that the repair shop offers this time granularity."

a9f634b888de4927babb91d7d2920579

[Advertisement] Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready.Learn more.

CodeSOD: Cancel Catch

30 April 2026 at 06:30

"This WTF is in Matlab" almost feels like cheating. At one place I worked, somebody's job was struggling through a mountain of Matlab code and porting it into C. "This Matlab code looks like it was written by an alien," also doesn't really get much traction- all Matlab code looks like it was written by an alien. This falls into the realm of "Researchers use Matlab, researchers may be very smart about their domain, but generally don't know the first thing about writing maintainable code, because that's not their job."

But let's take a look at some MatLab Carl W found:

    try
        if (~isempty(fieldnames(bigStruct)) && isfield(bigStruct,'pathName'))
            [FileName, PathName] = uigetfile(bigStruct.pathName);
        else
            [FileName, PathName] = uigetfile(lastPath); %lastPath holds previous path
        end
    catch
        bigStruct = struct;
    end

The uigetfile function opens a file dialog box. When the user selects a file, FileName holds the filename, PathName holds the containing path. If the user doesn't select a valid file, or clicks "Cancel", both of those variables get set to 0. It's then up to the caller to check the return value and decide what happens next.

Which is not what happens here, obviously. The developer responsible seems to believe that it maybe throws an exception? And they can just catch it? Carl's best guess is that this is a "weird" way to catch the cancel button. But it does mean that FileName and PathName get set to 0, and those zeros propagate until something finally tries to open those files, at which point everything blows up and the user doesn't know why.

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

A Whale of a Problem

29 April 2026 at 06:30

From our Anonymous submitter:

Our company creates graphs to visualize data. We have many small fish customers, but we have one whale who uses our product that is 90% of company revenue. (WTF number 1.)

So if he is not happy, it's all-hands-on deck-mode.

He complained that our APIs and charts are loading slowly for him. For 3 weeks, we've tried a TON of optimizations, including WTF 2: spinning up a special server he alone can hit.

Today, we found out that he's always complaining when he's in his car, driving from home to the office. But since he "totally has the best wifi money can buy," that isn't worth investigating.

WTF 3: thinking wifi and data are always 100% reliable in a car driving around.

Humpback whale breaching in Ballena Marine National Park

Our submitter highlights one of the major pitfalls of the so-called whale client: if they're a bad client, you're in for an extra-bad time.

As I lean harder into freelancing, I'm learning to scan the waters ahead of me for potential whales. My goal is to build up multiple small, diverse income streams, because I've had my own dangerous encounters with whales in the past.

At one employer of mine, there was Facebook, who acted as if they were our new owners rather than a new customer. They'd already produced flashy marketing videos of the sorts of solutions they planned to implement with our software, showing people delighted with the results. In meetings, these things were talked up as amazing game-changers. Meanwhile, I found all the things Facebook wanted to do horribly creepy and invasive.

Even worse, Facebook began dictating how our award-winning technical support should change to accommodate their whims, up to and including having a dedicated toady—er, support rep—who did nothing but field Facebook-related tickets, similar to a technical account manager (TAM).

That was the last straw for me. I left that company before I was forced to deal with any of Facebook's crap.

My second whale sighting occurred at a startup that'd landed Porsche, far and away their biggest client ever. All of a sudden, our timeline for adding new features and fixing bugs became Porsche's honey-do list. All of a sudden, the platform frequently crashed and became unusable for everyone because it couldn't handle the amount of traffic Porsche (and their clients) hurled at it.

On the other hand, there were several times in that startup's existence when a big wad of promised funding failed to materialize. Porsche kept the business afloat and literally kept my lights on.

I find it less than ideal to be at any company's mercy. I want a world that would neither spawn whales nor millions of startups named Sploink, Dink, and Twangle that promise to bring the power of AI to your dinner fork.

Have your own epic whaling adventures? Share with us in the comments!

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

CodeSOD: Lint Brush Off

28 April 2026 at 06:30

A few years back, C# added the concept of "primary constructors". Instead of declaring the storage for class members and then initializing them in the constructor, you can annotate the class itself with the required fields, and C# automatically generates a constructor for you. It's all very TypeScript and very Microsoft, and certainly cuts down on some boilerplate.

Esben B's team isn't really using them in many places, but they are using a linter which is opinionated about them. So this in-line constructor causes the linter to complain:

    public DocumentNetworkController(ILookupClient service)

The linter wants you to switch this to a primary constructor. Esben didn't want to do that, and didn't want to change the global linter configuration, and so added a pragma to disable that particular warning:

#pragma warning disable IDE0290 // Use primary constructor
    public DocumentNetworkController(ILookupClient service)
#pragma warning restore IDE0290

The linter didn't like this. It threw a new warning: that this suppression wasn't needed. Which was news to Esben, as clearly the suppression was needed if you wanted to make the warnings go away. The obvious solution was to disable the warning that you didn't need to disable the warning:

#pragma warning disable IDE0079, IDE0290 // Use primary constructor
    public DocumentNetworkController(ILookupClient service)
#pragma warning restore IDE0290, IDE0079

Except this doesn't work. These pragmas take effect on the next line, which means you can't disable IDE0079 on the same line as IDE0290 and expect it to work. Which means the final version of the code looked like this:

#pragma warning disable IDE0079 // Disable warning about not needed supression
#pragma warning disable IDE0290 // Use primary constructor
    public DocumentNetworkController(ILookupClient service)
#pragma warning restore IDE0290, IDE0079

Esben writes:

So the nice recommendation to use a primary ctor ended up with 3 lines of annoying boilerplate code. Good times \o/

While yes, this is frustrating, I will say there's an element of "when the table saw keeps taking fingers off, that may be more of a you problem." I don't know the details, so I can't say, "just change the linter config or adopt its recommendation" and claim that the problem goes away, but when the tool hurts you, it's a definite sign of one of two things: it's either the wrong tool, or you're using it wrong.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

CodeSOD: The JSON Template

27 April 2026 at 06:30

We rip on PHP a lot, but I am willing to admit that the language and ecosystem have evolved over the years. What started as an ugly templating language is now just an ugly regular language.

But what happens when you still really want to do things with templates? Allison has inherited a Python-based, WSGI application which rejects any sort of formal routing or basic web development best practices. Their way of routing requests is simply long chains of "if condition then invokeA elif otherCondition then invokeB". Sometimes, those conditions will directly set the MIME type on the HTTP response.

They do use a templating library called Mako for generating their responses. They use it for their HTML responses, obviously. They also use it for their JSON responses, generating code like this:

{
    "success": true,
    "items": {
        %for item in items_available.keys():
        "${item}": ${items_available[item]}${',' if not loop.last else ''} 
        %endfor
        }   
}

The %for and matching %endfor mark the Python code off, which generates JSON via string-munging, complete with the check to make sure we're not on the last iteration of the loop.

Like so much bad code, this offers a degree of fractal wrongness. Instead of iterating over the keys and fetching the items inside the loop, you could iterate for key,value in items_available.items()- and according to the Mako docs, that for is just a regular Python for loop. That we're just outputting the contents of the dictionary is itself potentially a problem- sure, if we know the types of the dictionary, we'll know that whatever it is can be output in the body of a JSON document, but do we really think this code is using type annotations? I don't. And for a RESTful web service, I'm always going to feel weird about using a success field when ideally the HTTP status code could convey most of that information (and yes, I know there are reasons to still put status in the body, I just hate it).

Of course, the real issue is just: Python's built in JSON serialization is actually pretty advanced. And performant! You don't need any of this, you could just do something like:

return json.dumps({"success": true, "items": items_available})

No templates. No formatting. No worries about how the data gets represented. Well, still worries, because JSON serialier will throw exceptions if it doesn't know what to do with a type. But then at least you get that exception on the server side and aren't sending the client a malformed document.

In any case, this is a good demonstration that you can write bad PHP in any language.

[Advertisement] Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready.Learn more.

Error'd: April Showers

24 April 2026 at 06:30

"RFC 1738 (and 3986) disagree" and so does Daniel D. "Reddit API has some weird app creation going on with lots of recently migrated and undocumented stuff. But having redirect URL set to localhost (or 127.0.0.1) usually works. Well, if you don't disagree with Sir Tim Berners-Lee about what URL is. Which Reddit does. hostnumber = digits "." digits "." digits "." digits". I'd file this one with all the websites that try to perform validation on email addresses, and get it wrong.

ad5bfafde9a74b7a8c38d429a364be48

"Why aren't we getting any resumes?" wondered Fred G. "This is a snippet from a job posting. I'm sure it worked perfectly when HR tested it."

2c21d5766e724b9095103c6c537adfa3

"Service required..." was Chris H.'s title for this gem. "My 2022 Chevrolet has been at the dealer for recall service for two weeks now, "waiting for parts". That doesn't stop GM from emailing every few days with a reminder that the car needs the recall service, and inviting me to schedule it at a dealer (that isn't actually a dealer) located a convenient 2500 mile drive from my home (about 200 times the distance to the dealer where the car currently sits), and providing a non-existent placeholder phone number to contact them at to schedule the recall service."

78cac2590ecf4996a2f4ee79e0b38b49

"How to subtly tell your customers that you don't wish to be contacted" explains Yuri. "The bank's staff must be wondering why no one wants to talk to them...Is it their suit's brand that is throwing everyone off? Can they blame it on COVID?"

81b84743c3a9405f8ed25c9c18b86029

"Bad money formatting by tax software" Adam R. complained. "I'm ashamed to admit it, but yes, I did pay Intuit money to file my taxes. This should really be a free service provided by the government, but, y'know, *lobbying*. You'd think that a business focused on tax preparation software would know how to properly format currency values, but in this case they failed to set the proper number of decimal points."

a9085ecfb2d2403ebd3d856e0c2a1179

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

CodeSOD: Tune Out the Static

23 April 2026 at 06:30

Henrik H (previously) sends us a simple representative C# line:

static void GenerateCommercilaInvoice()

This is a static method which takes no parameters and returns nothing. Henrik didn't share the implementation, but this static function likely does something that involves side effects, maybe manipulating the database (to generate that invoice?). Or, possibly worse, it could be doing something with some global or static state. It's all side effects and no meaningful controls, so enjoy debugging that when things go wrong. Heck, good luck testing it. Our best case possibility is that it's just a wrapper around a call to a stored procedure.

This method signature is basically a commercila for refactoring.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

Representative Line: Comment Overflow

22 April 2026 at 06:30

Today, we look at a representative comment, sent to us by Nona. This particular comment was in a pile of code delivered by an offshore team.

// https://stackoverflow.com/questions/46744740/lodash-mongoose-object-id-difference/46745169

"Wait," you say, "what's the WTF about a comment pointing to a Stack Overflow page. I do that all the time?"

In this case, it's because this particular comment wasn't given any further explanation. It also wasn't in a block of code that was doing anything with either lodash, Mongoose, or set differences. It was, however, repeated multiple times throughout the codebase, because the entire codebase was a pile of copy-pasta glued together with the bare minimum code to make it work.

In at least one place, the comment was probably correct and helpful. But it got swept up as part of a broader copy/paste exercise, and now is scattered through the code without any true purpose.

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

Turning Thirty

21 April 2026 at 06:30

Eric O worked for a medical device company. The medical device industry moves slowly, relative to other technical industries. Medical science and safety have their own cadence, and at a certain point, iterating faster doesn't matter much.

Eric was working on a new feature on a system that had been in use for thirteen years. This new feature interacted with a database which stored information about racks of test tubes, and Eric's tests meant creating several entries for racks of test tubes. And that's when Eric discovered that the database only allowed thirty racks. Add any more, it would just roll right back over to one.

This was odd. The database was small- less than 40MB, even in production- and there were automatic tasks to purge old data for compliance purposes. Why a hard limit of thirty?

Eric had only been at the company for a year, so he asked one of the more senior team members, Lester. "Oh yeah, that was before my time. You should probably ask Carl."

Later that day, Eric happened to bump into Carl around the coffee maker, and asked the question. "Oh, yeah, I do vaguely remember something about that. It was in the requirements for the product. I thought it was weird, but didn't think too much about it. You should probably ask Elise, she's been here like twenty years."

Well, now it was getting curious. Eric went over to the "old building", as it was named, the original office for the company on the other side of the parking lot. Most of the offices had moved to the new building a decade earlier, and it mostly served as fabrication and storage, but a few offices remained.

Elise was on the third floor, down a poorly lit hallway, sitting in an office with water-stained acoustical tile in its ceiling. "Oh, yeah, I put that into the requirements document. It's funny, I thought it was weird too, but the system you're working on was a replacement for an older system. Our requirements were derived from those. Let me think… Irving worked on that, but he's dead, god rest him. Penny is retired. Oh, you know, Humbert is still around. He didn't work on that, but he worked on some of the systems that came before that. He's upstairs and on the other side of the building."

Eric went upstairs and to the other side of the building. The fourth floor had been last remodeled circa 1985, and the ugly industrial paint on the wall was made even uglier by the fact that someone had replaced most of the flourescent tubes with LEDs. Most. The mismatched color temperature started Eric down the path of a headache.

Humbert was in an office similar to Elise's. On his desk was a plaque commemerating 40 years of service with the company. Eric asked about the limitation, and Humbert laughed.

"You're working on the latest version of a product that initially started on an old PDP-11 running MUMPS. I mean, the first versions, anyway. We ran to desktop computers as fast as we could. I wrote a version for DOS in… oh… '86? I knew none of the facilities we worked with had more than ten or fifteen racks of tubes, and I needed somehow to limit the size of the database so it all fit on a single 5 1/4" floppy disk. I picked thirty, because it seemed like a good round number. Honestly, I'm shocked that the limit still exists."

So was Eric. There had been several ground-up-rewrites since 1986, before the one Eric maintained had been released thirteen years ago. Each one of them had chosen to maintain the same limitation, without ever considering why it existed. The rule had simply been copied, mindlessly, for 40 years.

"I'm kind of impressed," Eric said to Humbert, "in a horrified way."

"Me too, kid, me too."

[Advertisement] Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready.Learn more.

CodeSOD: Good Etiquette

20 April 2026 at 06:30

"Here, you're a programmer, take this over. It's business critical."

That's what Felicity's boss told her when he pointed her to a network drive containing an Excel spreadsheet. The Excel spreadsheet contained a pile of macros. The person who wrote it had left, and nobody knew how to make it work, but the macros in question were absolutely business vital.

Also, it's in French.

We'll take this one in chunks. The indentation is as in the original.

Public Sub ExporToutVersBaseDonnées(ClasseurEnCours As Workbook)
Call AffectionVariables(ToutesLesCellulesNommées)
Call AffectationBaseDonnées(BaseDonnées)
BaseDonnées.Activate

The procedures AffectionVariables and AffectationBaseDonnées populate a pile of global variables. "base de données" is French for database, but don't let the name fool you- anything referencing "base de données" is referencing another Excel file located on a shared server. There are, in total, four Excel files that must live on a shared server, and two more which must be in a hard-coded path on the user's computer.

Oh, and the shared server is referenced not by a hostname, but by IP address- which is why the macros were breaking on everyone's computer; the IP address changed.

Let's continue.

'Vérifier si la ligne existe déjà.
        If ClasseurEnCours.Sheets("DATA").Range("Num_Fichier") = 0 Then
        Num_Fichier = BaseDonnées.Sheets(1).Range("Dernier_Fichier").Value + 1
Insérer_Ligne: '(étiquette Goto) insérer une ligne
    Application.GoTo Reference:="Dernière_Ligne"
            Selection.EntireRow.Insert
'Copie les cellules (colonne A à colonne FI) de la ligne au-dessus de la ligne insérée.
            With ActiveCell
                    .Offset(-1, 0).Range("A1:FM1").Copy
'Colle le format de la cellule précédemment copiée à la cellule active puis libère les données du presse papier
                    .PasteSpecial
                    .Range("A1:FM1").Value = ""
'Se repositionne au début de la ligne insérée.
                    .Range("A1").Select
            End With
            Application.CutCopyMode = False

Uh oh, Insérer_Ligne is a label for a Goto target. Not to be confused by the Application.GoTo call on the next line- that just selects a range in the spreadsheet.

After that little landmine, we copy/paste some data around in the sheet.

That's the If side of the conditional, let's look at the else clause:

        Else
Cherche_Numéro_Fichier: ' Chercher la ligne ou le numéro de fichier est égale à NumFichier.
                        While ActiveCell.Value <> Num_Fichier
                If ActiveCell.Row = Range("Etiquettes").Row Then
                    GoTo Insérer_Ligne
                End If
                ActiveCell.Offset(-1, 0).Range("a1:a1").Select
            Wend
            'Vérifier le numéro d'indice de la ligne active.
                If Cells(ActiveCell.Row, 165).Value <> ClasseurEnCours.Sheets("DATA").Range("Dernier_Indice") Then
                    ActiveCell.Offset(-1, 0).Range("A1:A1").Select
                    GoTo Cherche_Numéro_Fichier
                End If
            ActiveCell.Offset(0, 0).Range("A1:FM1").Value = ""
        End If

We start with another label, and… then we have a Goto. A Goto which jumps us back into the If side of the conditional. A Goto inside of a while loop, a while loop that's marching around the spreadsheet to search for certain values in the cell.

After the loop, we have another Goto which will possibly jump us up to the start of the else block.

The procedure ends with some cleanup:

'----- 
' Do some stuff on the active cell and the following cells on the column
.-----
BaseDonnées.Close True
Set BaseDonnées = Nothing
End Sub

I do not know what this function does, and the fact that the code is largely in a language I don't speak isn't the obstacle. I have no idea what the loops and the gotos are trying to do. I'm not even a "never use Goto ever ever ever" person; in a language like VBA, it's sometimes the best way to handle errors. But this bizarre time-traveling flow control boggles me.

"Etiquettes" is French for "labels", and it may be bad etiquette but I've got some four letter labels for this code.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

Error'd: Having a Beastly Time

17 April 2026 at 06:30

It's time again for a reader special, and once again it's all The Beast In Black (there must be a story to that nick, no?).

"MySQL is not better than your SQL," he pontificated, "especially when it comes to the Workbench Migration Wizard"

7369002fc20e41b89b64ed7f32ef3641

"Sadly," says he, "Not even gmail/chromium either."

149c9109443f4521b1a38b91bd0bcc22

"Updated software is available, but there are no updates!" he puzzled. "Clicking Install Now just throws that dialog right back in my face. I'm re-cursing." Zero, one, does it really make a difference?

e9ea57c886984dc8a106df503a2fd923

"Questions" The Beast in Black "I do, in fact, have a question..."

f5c83f7bc02644a895f1f9aa5ec368a1

One of the foundational guides to my [lyle, not bib] engineering career was John Bentley's Programming Pearls. These are not those.
"Veni, vidi: vc. No pearls of wisdom here, just litter." says The Beast.

4e13a188deb94473abcc6148d106458c

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

CodeSOD: We'll Hire Better Contractors Next Time, We Promise

16 April 2026 at 06:30

Nona writes: "this is the beginning of a 2100 line function."

That's bad. Nona didn't send us the entire JavaScript function, but sent us just the three early lines, which definitely raise concerns:

if (res.length > 0) {
  await (function () {
    return new Promise((resolve, reject) => {

We await a synchronous function which retuns a promise, passing a function to the promise. As a general rule, you don't construct promises directly, you let asynchronous code generate them and pass them around (or await them). It's not a thing you never do, but it's certainly suspicious. It gets more problematic when Nona adds:

This function happens to contain multiple code repetition snippets, including these three lines.

That's right, this little block appears multiple times in the function, inside of anonymous function getting passed to the Promise.

No, the code does not work in its current state. It's unclear what the 2100 line function was supposed to do. And yes, this was written by lowest-bidder third-party contractors.

Nona adds:

I am numb at this point and know I gotta fix it or we lose contracts

Management made the choice to "save money" by hiring third parties, and now Nona's team gets saddled with all the crunch to fix the problems created by the "savings".

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

CodeSOD: Awaiting A Reaction

12 March 2026 at 06:30

Today's Anonymous submitter sends us some React code. We'll look at the code and then talk about the WTF:

// inside a function for updating checkboxes on a page
if (!e.target.checked) {
  const removeIndex = await checkedlist.findIndex(
    (sel) => sel.Id == selected.Id,
  )
  const removeRowIndex = await RowValue.findIndex(
    (sel) => sel == Index,
  )

// checkedlist and RowValue are both useState instances.... they should never be modified directly
  await checkedlist.splice(removeIndex, 1)
  await RowValue.splice(removeRowIndex, 1)

// so instead of doing above logic in the set state, they dont
  setCheckedlist(checkedlist)
  setRow(RowValue)
} else {
  if (checkedlist.findIndex((sel) => sel.Id == selected.Id) == -1) {
    await checkedlist.push(selected)
  }
// same, instead of just doing a set state call, we do awaits and self updates
  await RowValue.push(Index)
  setCheckedlist(checkedlist)
  setRow(RowValue)
}

Comments were added by our submitter.

This code works. It's the wrong approach for doing things in React: modifying objects controlled by react, instead of using the provided methods, it's doing asynchronous push calls. Without the broader context, it's hard to point out all the other ways to do this, but honestly, that's not the interesting part.

I'll let our submitter explain:

This code is black magic, because if I update it, it breaks everything. Somehow, this is working in perfect tandem with the rest of the horrible page, but if I clean it up, it breaks the checkboxes; they're no longer able to be clicked. Its forcing React somehow to update asynchronously so it can use these updated values correctly, but thats the neat part, they aren't even being used anywhere else, but somehow the re-rendering page only accepts awaits. I've tried refactoring it 5 different ways to no avail

That's what makes truly bad code. Code so bad that you can't even fix it without breaking a thousand other things. Code that you have to carefully, slowly, pick through and gently refactor, discovering all sorts of random side-effects that are hidden. The code so bad that you actually have to live with it, at least for awhile.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

CodeSOD: All Docked Up

11 March 2026 at 06:30

Aankhen has a peer who loves writing Python scripts to automate repetitive tasks. We'll call this person Ernest.

Ernest was pretty proud of some helpers he wrote to help him manage his Docker containers. For example, when he wanted to stop and remove all his running Docker containers, he wrote this script:

#!/usr/bin/env python
import subprocess

subprocess.run("docker kill $(docker ps -q)", shell=True)
subprocess.run("docker rm $(docker ps -a -q)", shell=True)

He aliased this script to docker-stop, so that with one command he could… run two.

"Ernest," Aankhen asked, "couldn't this just be a bash script?"

"I don't really know bash," Ernest replied. "If I just do it in bash, if the first command fails, the second command doesn't run."

Aankhen pointed out that you could make bash not do that, but Ernest replied: "Yeah, but I always forget to. This way, it handles errors!"

"It explicitly doesn't handle errors," Aankhen said.

"Exactly! I don't need to know when there are no containers to kill or remove."

"Okay, but why not use the Docker library for Python?"

"What, and make the software more complicated? This has no dependencies!"

Aankhen was left with a sinking feeling: Ernest was either the worst developer he was working with, or one of the best.

[Advertisement] Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready.Learn more.

CodeSOD: To Shutdown You Must First Shutdown

10 March 2026 at 06:30

Every once in awhile, we get a bit of terrible code, and our submitter also shares, "this isn't called anywhere," which is good, but also bad. Ernesto sends us a function which is called in only one place:

///
/// Shutdown server
///
private void shutdownServer()
{
    shutdownServer();
}

The "one place", obviously, is within itself. This is the Google Search definition of recursion, where each recursive call is just the original call, over and over again.

This is part of a C# service, and this method shuts down the server, presumably by triggering a stack overflow. Unless C# has added tail calls, anyway.

[Advertisement] BuildMaster allows you to create a self-service release management platform that allows different teams to manage their applications. Explore how!

Anti-Simplification

9 March 2026 at 06:30

Our anonymous submitter relates a tale of simplification gone bad. As this nightmare unfolds, imagine the scenario of a new developer coming aboard at this company. Imagine being the one who has to explain this setup to said newcomer.

Imagine being the newcomer who inherits it.

A "Storm P machine" - the Danish equivalent of a Rube Goldberg machine.

David's job should have been an easy one. His company's sales data was stored in a database, and every day the reporting system would query a SQL view to get the numbers for the daily key performance indicators (KPIs). Until the company's CTO, who was proudly self-taught, decided that SQL views are hard to maintain, and the system should get the data from one of those new-fangled APIs instead.

But how does one call an API? The reporting system didn't have that option, so the logical choice was Azure Data Factory to call the API, then output the data to a file that the reporting system could read. The only issue was that nobody on the team spoke Azure Data Factory, or for that matter SQL. But no problem, one of David's colleagues assured, they could do all the work in the best and most multifunctional language ever: C#.

But you can't just write C# in a data factory directly, that would be silly. What you can do is have the data factory pipeline call an Azure function, which calls a DLL that contains the bytecode from C#. Oh, and a scheduler outside of the data factory to run the pipeline. To read multiple tables, the pipeline calls a separate function for each table. Each function would be based on a separate source project in C#, with 3 classes each for the HTTP header, content, and response; and a separate factory class for each of the actual classes.

After all, each table had a different set of columns, so you can't just re-use classes for that.

There was one little issue: the reporting system required an XML file, whereas the API would export data in JSON. It would be silly to expect a data factory, of all things, to convert this. So the CTO's solution was to have another C# program (in a DLL called by a function from a pipeline from an external scheduler) that reads the JSON document saved by the earlier program, uses foreach to go over each element, then saves the result as XML. A distinct program for each table, of course, requiring distinct classes for header, content, response, and factories thereof.

Now here's the genius part: to the C# class representing the output data, David's colleague decided to attach one different object for each input table required. The data class would use reflection to iterate over the attached objects, and for each object, use a big switch block to decide which source file to read. This allows the data class to perform joins and calculations before saving to XML.

To make testing easier, each calculation would be a separate function call. For example, calculating a customer's age was a function taking struct CustomerWithBirthDate as input, use a foreach loop to copy all the data except replacing one field, and return a CustomerWithAge struct to pass to the next function. The code performed a bit slowly, but that was an issue for a later year.

So basically, the scheduler calls the data factory, which calls a set of Azure functions, which call a C# function, which calls a set of factory classes to call the API and write the data to a text file. Then, the second scheduler calls a data factory, which calls Azure functions, which call C#, which calls reflection to check attachment classes, which read the text files, then call a series of functions for each join or calculation, then call another set of factory classes to write the data to an XML file, then call the reporting system to update.

Easy as pie, right? So where David's job could have been maintaining a couple hundred lines of SQL views, he instead inherited some 50,000 lines of heavily-duplicated C# code, where adding a new table to the process would easily take a month.

Or as the song goes, Somebody Told Me the User Provider should use an Adaptor to Proxy the Query Factory Builder ...

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.

Error'd: That's What I Want

6 March 2026 at 06:30

First up with the money quote, Peter G. remarks "Hi first_name euro euro euro, look how professional our marketing services are! "

1

 

"It takes real talent to mispell error" jokes Mike S. They must have done it on purpose.

0

 

I long wondered where the TikTok profits came from, and now I know. It's Daniel D. "I had issues with some incorrectly documented TikTok Commercial Content API endpoints. So I reached out to the support. I was delighted to know that it worked and my reference number was . PS: 7 days later I still have not been contacted by anyone from TikTok. You can see their support is also . "

2

 

Fortune favors the prepared, and Michael R. is very fortunate. "I know us Germans are known for planning ahead so enjoy the training on Friday, February 2nd 2029. "

3

 

Someone other than dragoncoder047 might have shared this earlier, but this time dragoncoder047 definitely did. "Digital Extremes (the developers of Warframe) were making many announcements of problems with the new update that rolled out today [February 11]. They didn’t mention this one!"

4

 

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

CodeSOD: Qaudruple Negative

5 March 2026 at 06:30

We mostly don't pick on bad SQL queries here, because mostly the query optimizer is going to fix whatever is wrong, and the sad reality is that databases are hard to change once they're running; especially legacy databases. But sometimes the code is just so hamster-bowling-backwards that it's worth looking into.

Jim J has been working on a codebase for about 18 months. It's a big, sprawling, messy project, and it has code like this:

AND CASE WHEN @c_usergroup = 50 AND NOT EXISTS(SELECT 1 FROM l_appl_client lac WHERE lac.f_application = fa.f_application AND lac.c_linktype = 840 AND lac.stat = 0 AND CASE WHEN ISNULL(lac.f_client,0) <> @f_client_user AND ISNULL(lac.f_c_f_client,0) <> @f_client_user THEN 0 ELSE 1 END = 1 ) THEN 0 ELSE 1 END = 1 -- 07.09.2022

We'll come back to what it's doing, but let's start with a little backstory.

This code is part of a two-tier application: all the logic lives in SQL Server stored procedures, and the UI is a PowerBuilder application. It's been under development for a long time, and in that time has accrued about a million lines of code between the front end and back end, and has never had more than 5 developers working on it at any given time. The backlog of feature requests is nearly as long as the backlog of bugs.

You may notice the little date comment in the code above. That's because until Jim joined the company, they used Visual Source Safe for version control. Visual Source Safe went out of support in 2005, and let's be honest: even when it was in support it barely worked as a source control system. And that's just the Power Builder side- the database side just didn't use source control. The source of truth was the database itself. When going from development to test to prod, you'd manually export object definitions and run the scripts in the target environment. Manually. Yes, even in production. And yes, environments did drift and assumptions made in the scripts would frequently break things.

You may also notice the fields above use a lot of Hungarian notation. Hungarian, in the best case, makes it harder to read and reason about your code. In this case, it's honestly fully obfuscatory. c_ stands for a codetable, f_ for entities. l_ is for a many-to-many linking table. z_ is for temporary tables. So is x_. And t_. Except not all of those "temporary" tables are truly temporary, a lesson Jim learned when trying to clean up some "junk" tables which were not actually junk.

I'll let Jim add some more detail around these prefixes:

an "application" may have a link to a "client", so there is an f_client field; but also it references an "agent" (which is also in the f_client table, surpise!) - this is how you get an f_c_f_client field. I have no clue why the prefix is f_c_ - but I also found c_c_c_channel and fc4_contact columns. The latter was a shorthand for f_c_f_c_f_c_f_contact, I guess.

"f_c_f_c_f_c_f_c" is also the sound I'd make if I saw this in a codebase I was responsible for. It certainly makes me want to change the c_c_c_channel.

With all this context, let's turn it back over to Jim to explain the code above:

And now, with all this background in mind, let's have a look at the logic in this condition. On the deepest level we check that both f_client and f_c_f_client are NOT equal to @f_client_user, and if this is the case, we return 0 which is NOT equal to 1 so it's effectively a negation of the condition. Then we check that records matching this condition do NOT EXIST, and when this is true - also return 0 negating the condition once more.

Honestly, the logic couldn't be clearer, when you put it that way. I jest, I've read that twelve times and I still don't understand what this is for or why it's here. I just want to know who we can prosecute for this disaster. The whole thing is a quadruple negative and frankly, I can't handle that kind of negativity.

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

CodeSOD: Repeating Your Existence

4 March 2026 at 06:30

Today's snippet from Rich D is short and sweet, and admittedly, not the most TFs of WTFs out there. But it made me chuckle, and sometimes that's all we need. This Java snippet shows us how to delete a file:

if (Files.exists(filePath)) {
    Files.deleteIfExists(filePath);
}

If the file exists, then if it exists, delete it.

This commit was clearly submitted by the Department of Redundancy Department. One might be tempted to hypothesize that there's some race condition or something that they're trying to route around, but if they are, this isn't the way to do it, per the docs: "Consequently this method may not be atomic with respect to other file system operations." But also, I fail to see how this would do that anyway.

The only thing we can say for certain about using deleteIfExists instead of delete is that deleteIfExists will never throw a NoSuchFileException.

[Advertisement] Plan Your .NET 9 Migration with Confidence
Your journey to .NET 9 is more than just one decision.Avoid migration migraines with the advice in this free guide. Download Free Guide Now!

CodeSOD: Blocked Up

3 March 2026 at 06:30

Agatha has inherited some Windows Forms code. This particular batch of such code falls into that delightful category of code that's wrong in multiple ways, multiple times. The task here is to disable a few panels worth of controls, based on a condition. Or, since this is in Spanish, "bloquear controles". Let's see how they did it.

private void BloquearControles()
{
	bool bolBloquear = SomeConditionTM; // SomeConditionTM = a bunch of stuff. Replaced for clarity.

	// Some code. Removed for clarity.
	
	// private System.Windows.Forms.Panel pnlPrincipal;
	foreach (Control C in this.pnlPrincipal.Controls)
	{
		if (C.GetType() == typeof(System.Windows.Forms.TextBox))
		{
			C.Enabled = bolBloquear;
		}
		if (C.GetType() == typeof(System.Windows.Forms.ComboBox))
		{
			C.Enabled = bolBloquear;
		}
		if (C.GetType() == typeof(System.Windows.Forms.CheckBox))
		{
			C.Enabled = bolBloquear;
		}
		if (C.GetType() == typeof(System.Windows.Forms.DateTimePicker))
		{
			C.Enabled = bolBloquear;
		}
		if (C.GetType() == typeof(System.Windows.Forms.NumericUpDown))
		{
			C.Enabled = bolBloquear;
		}
	}
	
	// private System.Windows.Forms.GroupBox grpProveedor;
	foreach (Control C1 in this.grpProveedor.Controls)
	{
		if (C1.GetType() == typeof(System.Windows.Forms.TextBox))
		{
			C1.Enabled = bolBloquear;
		}
		if (C1.GetType() == typeof(System.Windows.Forms.ComboBox))
		{
			C1.Enabled = bolBloquear;
		}
		if (C1.GetType() == typeof(System.Windows.Forms.CheckBox))
		{
			C1.Enabled = bolBloquear;
		}
		if (C1.GetType() == typeof(System.Windows.Forms.DateTimePicker))
		{
			C1.Enabled = bolBloquear;
		}
		if (C1.GetType() == typeof(System.Windows.Forms.NumericUpDown))
		{
			C1.Enabled = bolBloquear;
		}
	}

	// private System.Windows.Forms.GroupBox grpDescuentoGeneral;
	foreach (Control C2 in this.grpDescuentoGeneral.Controls)
	{
		if (C2.GetType() == typeof(System.Windows.Forms.TextBox))
		{
			C2.Enabled = bolBloquear;
		}
		if (C2.GetType() == typeof(System.Windows.Forms.ComboBox))
		{
			C2.Enabled = bolBloquear;
		}
		if (C2.GetType() == typeof(System.Windows.Forms.CheckBox))
		{
			C2.Enabled = bolBloquear;
		}
		if (C2.GetType() == typeof(System.Windows.Forms.DateTimePicker))
		{
			C2.Enabled = bolBloquear;
		}
		if (C2.GetType() == typeof(System.Windows.Forms.NumericUpDown))
		{
			C2.Enabled = bolBloquear;
		}
	}

	// Some more code. Removed for clarity.
}

This manages two group boxes and a panel. It checks a condition, then iterates across every control beneath it, and sets their enabled property on the control. In order to do this, it checks the type of the control for some reason.

Now, a few things: every control inherits from the base Control class, which has an Enabled property, so we're not doing this check to make sure the property exists. And every built-in container control automatically passes its enabled/disabled state to its child controls. So there's a four line version of this function where we just set the enabled property on each container.

This leaves us with two possible explanations. The first, and most likely, is that the developer responsible just didn't understand how these controls worked, and how inheritance worked, and wrote this abomination as an expression of that ignorance. This is extremely plausible, extremely likely, and honestly, our best case scenario.

Because our worse case scenario is that this code's job isn't to disable all of the controls. The reason they're doing type checking is that there are some controls used in these containers that don't match the types listed. The purpose of this code, then, is to disable some of the controls, leaving others enabled. Doing this by type would be a terrible way to manage that, and is endlessly confusing. Worse, I can't imagine how this behavior is interpreted by the end users; the enabling/disabling of controls following no intuitive pattern, just filtered based on the kind of control in use.

The good news is that Agatha can point us towards the first option. She adds:

They decided to not only disable the child controls one by one but to check their type and only disable those five types, some of which aren't event present in the containers. And to make sure this was WTF-worthy the didn't even bother to use else-if so every type is checked for every child control

She also adds:

At this point I'm not going to bother commenting on the use of GetType() == typeof() instead of is to do the type checking.

Bad news, Agatha: you did bother commenting. And even if you didn't, don't worry, someone would have.

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

CodeSOD: Popping Off

2 March 2026 at 06:30

Python is (in)famous for its "batteries included" approach to a standard library, but it's not that notable that it has plenty of standard data structures, like dicts. Nor is in surprising that dicts have all sorts of useful methods, like pop, which removes a key from the dict and returns its value.

Because you're here, reading this site, you'll also be unsurprised that this doesn't stop developers from re-implementing that built-in function, badly. Karen sends us this:

def parse_message(message):
    def pop(key):
        if key in data:
            result = data[key]
            del data[key]
            return result
        return ''

    data = json.loads(message)
    some_value = pop("some_key")
    # <snip>...multiple uses of pop()...</snip>

Here, they create an inner method, and they exploit variable hoisting. While pop appears in the code before data is declared, all variable declarations are "hoisted" to the top. When pop references data, it's getting that from the enclosing scope. Which while this isn't a global variable, it's still letting a variable cross between two scopes, which is always messy.

Also, this pop returns a default value, which is also something the built-in method can do. It's just the built-in version requires you to explicitly pass the value, e.g.: some_value = data.pop("some_key", "")

Karen briefly wondered if this was a result of the Python 2 to 3 conversion, but no, pop has been part of dict for a long time. I wondered if this was just an exercise in code golf, writing a shorthand function, but even then- you could just wrap the built-in pop with your shorthand version (not that I'd recommend such a thing). No, I think the developer responsible simply didn't know the function was there, and just reimplemented a built-in method badly, as so often happens.

[Advertisement] Picking up NuGet is easy. Getting good at it takes time. Download our guide to learn the best practice of NuGet for the Enterprise.

Error'd: Perverse Perseveration

27 February 2026 at 06:30

Pike pike pike pike Pike pike pike.

Lincoln KC repeated "I never knew Bank of America Bank of America Bank of America was among the major partners of Bank of America."

4

 

"Extra tokens, or just a stutter?" asks Joel "An errant alt-tab caused a needless google search, but thankfully Gemini's AI summary got straight-to-the-point(less) info. It is nice to see the world's supply of Oxford commas all in once place. "

0

 

Alessandro M. isn't the first one to call us out on our WTFs. "It’s adorable how the site proudly supports GitHub OAuth right up until the moment you actually try to use it. It’s like a door with a ‘Welcome’ sign that opens onto a brick wall." Meep meep.

1

 

Float follies found Daniel W. doubly-precise. "Had to go check on something in M365 Admin Center, and when I was on the OneDrive tab, I noticed Microsoft was calculating back past the bit. We're in quantum space at this point."

2

 

Weinliebhaber Michael R. sagt "Our German linguists here will spot the WTF immediately where my local wine shop has not. Weiẞer != WEIBER. Those words mean really different things." Is that 20 euro per kilo, or per the piece?

3

 

[Advertisement] Utilize BuildMaster to release your software with confidence, at the pace your business demands. Download today!

CodeSOD: The Counting Machine

26 February 2026 at 06:30

Industrial machines are generally accompanied by "Human Machine Interfaces", HMIs. This is industrial slang for a little computerized box you use to control the industrial machine. All the key logic and core functionality and especially the safety functionality is handled at a deeper computer layer in the system. The HMI is just buttons users can push to interact with the machine.

Purchasers of those pieces of industrial equipment often want to customize that user interface. They want to guide users away from functions they don't need, or make their specific workflow clear, or even just brand the UI. This means that the vendor needs to publish an API for their HMI.

Which brings us to Wendy. She works for a manufacturing company which wants to customize the HMI on a piece of industrial equipment in a factory. That means Wendy has been reading the docs and poking at the open-sourced portions of the code, and these raise more questions than they answer.

For example, the HMI's API provides its own set of collection types, in C#. We can wonder why they'd do such a thing, which is certainly a WTF in itself, but this representative line raises even more questions than that:

Int32 Count { get; set; }

What happens if you use the public set operation on the count of items in a collection? I don't know. Wendy doesn't either, as she writes:

I'm really tempted to set the count but I fear the consequences.

All I can hear in my head when I think about "setting the Count" is: "One! One null reference exception! Two! TWO null reference exceptions! HA HA HA HA!"

Count von Count kneeling.png
By http://muppet.wikia.com/wiki/Count_von_Count

[Advertisement] Keep the plebs out of prod. Restrict NuGet feed privileges with ProGet. Learn more.

CodeSOD: Safegaurd Your Comments

25 February 2026 at 06:30

I've had the misfortune of working in places which did source-control via comments. Like one place which required that, with each section of code changed, you needed to add a comment with your name, the ticket number, and the reason the change was made. You know, the kind of thing you can just get from your source control service.

In their defense, that policy was invented for mainframe developers and then extended to everyone else, and their source control system was in Visual Source Safe. VSS was a) terrible, and b) a perennial destroyer of history, so maybe they weren't entirely wrong and VSS was the real WTF. I still hated it.

In any case, Alice's team uses more modern source control than that, which is why she's able to explain to us the story of this function:

public function calculateMassGrossPay(array $employees, Payroll $payroll): array
{
    // it shouldn't enter here, but if it does by any change, do nth
    return [];
}

Once upon a time, this function actually contained logic, a big pile of fairly complicated logic. Eventually, a different method was created which streamlined the functionality, but had a different signature and logic. All the callers were updated to use that method instead- by commenting out the line which called this one. This function had a comment added to the top: // it shouldn't enter here.

Then, the body of this function got commented out, and the return was turned into an empty array. The comment was expanded to what you see above. Then, eventually, the commented-out callers were all deleted. Years after that, the commented out body of this function was also deleted, leaving behind the skeleton you see here.

This function is not referenced anywhere else, not even in a comment. It's truly impossible for code to "enter here".

Alice writes: "Version control by commented out code does not work very well."

Indeed, it does not.

[Advertisement] ProGet’s got you covered with security and access controls on your NuGet feeds. Learn more.
❌
❌