DEV Community

Cover image for Your publish pipeline is green. Nobody can install your plugin.
Heinrich Neb
Heinrich Neb

Posted on • Originally published at cachly.dev

Your publish pipeline is green. Nobody can install your plugin.

Silent failure

For three weeks nobody could install your plugin. The publish job was green every single day. You find out when a user asks why the version is so old.

Three weeks green, zero installs

This happened to us. The publish job for our JetBrains plugin reported success on every run since the middle of July. The plugin was not in the marketplace at all.

The registry API answered with a 404. A search for the product name returned nothing. Meanwhile the build was green, the release notes were written, and the changelog was up to date.

Nobody noticed. Not the pipeline, not the dashboard, not us. The gap between the last good release and the discovery was three weeks.

Your pipeline is not lying to you

This is the part worth understanding, because it is why the same thing is probably waiting in your repository too.

A release pipeline has one job: get the artifact somewhere a stranger can install it. Almost every pipeline checks something else. It checks that the upload command exited zero.

Those two questions agree nearly always. Our publish step went further and did the sensible thing. It caught the failure, compared the error text against a list of known-harmless cases, and exited zero for those.

One of those cases was pending moderation. A new version sits in review before it becomes visible. Failing the build for that would be noise, so it was allowed through.

Here is the trap. A harmless transient state and a permanent block produce the same message. Once the plugin was stuck, every later run matched the same friendly pattern and reported success. The pipeline answered its question correctly. It was the wrong question.

The check that catches it, in about two minutes

Ask the store, not the pipeline. That is the whole idea, and you can add it today without changing anything else.

One: after publishing, fetch the public listing the way a stranger would. No credentials, no internal API, no authenticated client. Seeing what an outsider sees is the entire point.

Two: read the version from that response and compare it to the version in your manifest. Not to the version you just built, and not to a variable in the job. To the file that is checked in.

Three: print one line per place you publish to, and say which one you could not reach. Reached, not reached, or stale. Never a bare zero.

That last part matters more than it looks. A source that errors out and gets counted as zero results looks exactly like a quiet day.

Make it fail on purpose, before you trust it

A watcher nobody has seen go red is not a watcher. It is a hope with a log file.

Give it a fixture where the listing is stale and assert that it exits non-zero. Run it once and look at the red. It takes a few minutes and it is the only thing that separates a real check from a comfortable one.

We skipped this step once on a different watcher. It sat there for weeks reporting nothing, and reporting nothing was indistinguishable from everything being fine.

Once you see this shape, you see it everywhere

A cache file written into a workspace that the next run wipes clean. A daily report whose delta is computed against a seeded example row. A source that returns an HTTP error and gets counted as zero.

Every one of them is green. Every one of them delivers nothing.

The missing ingredient is always the same: a third state. Systems model success and failure and forget not measured. Without it, an outage is indistinguishable from a quiet day, and people act on the number anyway.

What you get for the two minutes

A three-week outage turns into a message the next morning. That is the entire trade.

It also catches the case no exit code can reach: the upload genuinely succeeded and the listing still did not update. From the outside those look identical, and from the outside is where your users are.

Our version reads three public endpoints, compares them to the manifests in the repository, and prints one line each. It carries no credentials. It runs once a day, whether or not we released anything.

The day we turned it on, it was red.


I build cachly — persistent memory for AI coding assistants, over MCP. ChatGPT and Claude remember your conversations. cachly remembers your codebase: the bug you fixed, why you chose Postgres, the deploy step that always breaks — including what your teammates learned. And every assistant you use reads the same memory.

Stop finding out from your users — give your assistant a memory that survives the session. Free tier, hosted in the EU: cachly.dev

Top comments (0)