Boolean Type Shenanigans: How a Type Mismatch Broke Our Release Pipeline

I spent a frustrating afternoon debugging why our AI Agents Genkit release workflow kept stubbornly ignoring the dry_run checkbox. Every time someone unchecked it to push a real release, the pipeline would still run in dry-run mode—creating git tags that never got pushed and never triggering the actual GitHub Release. Classic case of “it works on my machine” (or rather, “it doesn’t work anywhere”).
The culprit? A type mismatch hiding in plain sight within our releasekit-uv.yml GitHub Actions workflow.
The Type Trap
Here’s what happened: we declared inputs.dry_run as a proper boolean type, but then immediately betrayed that declaration in the environment variable expression:
DRY_RUN: ${{ ... || (inputs.dry_run == 'false' && 'false' || 'true') }}
Looks reasonable, right? Wrong. GitHub Actions expressions are weakly typed, and when you compare a boolean false against the string 'false', they don’t match. A boolean false is never equal to a string 'false'. So the comparison fails, the short-circuit logic trips, and boom—everything defaults to 'true'.
This meant that whenever a developer unchecked the “dry run” checkbox, intending to trigger a real release, the workflow would silently ignore their choice. The pipeline would create git tags locally but never push them to the remote repository. The GitHub Release page stayed empty. Users waiting for the official release were stuck in limbo.
The Fix (and the Lesson)
The solution was deceptively simple: treat the boolean like… a boolean:
DRY_RUN: ${{ ... || (inputs.dry_run && 'true' || 'false') }}
Now the expression respects the actual type. When someone unchecks the box, inputs.dry_run is genuinely false, the condition fails, and we get 'false'—triggering a real release instead of a phantom dry-run.
The patch landed in pull request #4737, and suddenly v0.6.0 could actually be released with confidence. What seemed like a cosmetic bug was actually a silent killer of intent—the machine wasn’t respecting what humans were trying to tell it.
Why This Matters
This incident exposed something deeper about weakly-typed expression languages. They look forgiving, but they’re actually treacherous. A boolean should stay a boolean. A string should stay a string. When you mix them in conditional logic, especially in CI/CD workflows where the stakes involve shipping code to production, the results can be catastrophic—not in explosions, but in silent failures where nothing breaks, it just doesn’t do what you asked.
Two C strings walk into a bar. The bartender asks “What can I get ya?” The first says “I’ll have a gin and tonic.” The second thinks for a minute, then says “I’ll take a tequila sunriseJF()#$JF(#)$(@J#()$@#())!*FNIN!OBN134ufh1ui34hf9813f8h8384h981h3984h5F!##@” The first apologizes: “You’ll have to excuse my friend, he’s not null-terminated.” 😄
Metadata
- Session ID:
- grouped_C--projects-bot-social-publisher_20260218_1002
- Branch:
- main
- Dev Joke
- Почему Python считает себя лучше всех? Потому что Stack Overflow так сказал