cidx is a declarative CI/CD runner I build (in Go) — you describe your pipeline once, it runs the same locally and in CI. This post stands on its own.
Here's how the week started. A public Docker image, on GitHub Container Registry. A clean ENTRYPOINT ["cidx"]. Release after release, green CI every time.
And no binary inside. Not once. Ever.
.dockerignore excluded bin/. The Dockerfile did COPY bin/cidx /usr/local/bin/cidx. So the path was absent from the build context, kaniko matched nothing — and did not error. Green pipeline, published release, ENTRYPOINT pointing at a file that wasn't there.
No test and no code review could have caught it. Everything, at every step, looked like it worked.
That's the best illustration of what v3.0.0 turned out to be — cidx's first major since v2.0.0. Five breaking changes, 70 commits, 55 issues closed in seven days. And reading them back cold, they all tell exactly the same story.
Five breaking changes, one motive
A breaking change usually removes something ugly, or renames, or modernises. Not here. Each of the five removes a feature that looked like it worked.
1. local_behavior = "no-push" — a dry-run with a promise it could not keep.
The documentation said:
local_behavior = "no-push"✅ Recommended for Docker
Build without push — validates the Dockerfile and build process
The code said otherwise: no-push set IsDryRun, exactly as dry-run did. The command was never handed to a backend. Nothing was built. The two modes were behaviourally identical, and the one promising more was the lie.
2. [branch] auto_cleanup — parsed, and read by nobody.
A config key that existed, was accepted, was documented. No code ever consulted it. You'd set it, re-run, nothing happened — and nothing said so.
3. Unknown cidx.toml keys — a typo indistinguishable from a valid setting.
[branch]
stale_dayz = 15 # typo of stale_days
typo_key = "whatever" # not a key at all
$ cidx validate
✓ Configuration is valid
stale_days silently kept its default of 30, and nothing said why. You had configured something. The tool confirmed it was valid. It wasn't.
4. go-test didn't run every package. The preset tested less than it appeared to. The green bar described a wider scope than the real one.
5. The cidx action command tree — removed after a 3.5-month deprecation window. The only ordinary one of the five: it removes something announced as leaving, not something misleading.
Four out of five, then: not quality defects — honesty defects. A tool that prints ✓ valid over a typo doesn't waste your time by being slow. It wastes it by earning your trust.
Why they were findable now
These things don't show up by reading code: everything compiles, everything passes, everything is green. You need a mechanism that actively hunts the gap between what is promised and what happens.
The no-push lie was caught by a BDD sweep: a scenario asserted "the Docker image should be built" locally under no-push… and that scenario could never have passed. It wasn't code indicting code — it was a written promise held against real behaviour.
Same for the empty image: found while replacing the kaniko image, and confirmed pre-existing — identical on the old version, so not a regression. Nobody was looking for it; someone went and checked.
This is the direct sequel to what I wrote in July: cutting a release with the tool's own release command produced seven papercuts, all filed the same day. That was the thesis; this is the demonstration at major-release scale. Those seven are closed, along with forty-eight more.
The load-bearing one: /v3
One breaking change wasn't a deletion. The module path moved to github.com/cidx-org/cidx/v3 — without it, v3 tags simply aren't go installable. That's the Go modules rule: from v2 on, the major version is part of the path. Skip it and you publish a major nobody can install.
And the guardrail that came with it, in the same spirit as the rest: release create now refuses to cut a major the current module path cannot publish — rather than letting the tool produce a dead tag with a smile.
What it teaches
The same pattern showed up elsewhere the same week, in another project. In Verbose, the worst defect closed this week: the self-hosted compiler silently dropped every rule after any top-level item it didn't recognise — and exited 0. Your code wasn't compiled, and you were told everything was fine.
Two projects, two domains, one week, the same underlying defect: success reported while the work never happened.
A tool doesn't become reliable by accumulating features. It becomes reliable when you remove the ones that pretend. A deleted no-push is more honest than a no-push claiming to validate a build; a cidx.toml that rejects an unknown key is more useful than an agreeable ✓ valid; a missing image would have been less harmful than a published, empty one.
And the limit, named as usual: five issues remain open, all design questions rather than bugs. v3.1.0 landed the next morning (tag-rebuild detection, digest-pinned scanners, refusing to merge onto a commit the remote doesn't have). The pace will drop — two back-to-back sprint weeks is a window, not a cadence.
What's left is a simple triage rule going forward: when a feature and its documentation disagree, the documentation isn't the first thing to fix. The first question is how long you've been believing it.
French original on arcker.org.
Top comments (0)