Kai Ole Hartwig
6 min read
Low
By

Nothing happens without a reason

An update that does not come is the most expensive message in dependency automation. Because it never arrives. You only notice it when you expect a particular update and it stays away.

So pinup has a first, strict opinion: every held update names its reason, the rule holding it and the time the hold lifts. Absence is never just absence.

01 — The opinion in one sentence

An update that pinup finds and does not carry out is still in the plan. With a reason.

The reason is not free text but one of a dozen or so fixed values: schedule, minimumReleaseAge, dependencyDashboardApproval, taskRefused, the two limits and a few more. Next to it stands the origin: which file, which rule. Time-based holds also carry the moment they lift.

 

"blocks": [{
  "reason": "dependencyDashboardApproval",
  "origin": { "source": "file:.pinup.yaml", "rule": 3 }
}]

 

The dependency dashboard sorts the same by reason: approvals to tick, schedule windows and release ages with their date, everything else under “Held” with reason, rule and file, for instance taskRefused: … · held by packageRules[3] (file:.pinup.yaml). Whoever wants to know why an update is missing reads one line instead of a log.

02 — The alternative: the log

Renovate answers the same question differently. Some of it is on the dashboard: pending approvals, schedule windows, limits. Much of the rest is in the debug log. A post-upgrade command that the list of allowed commands does not admit is skipped, and the branch goes out anyway.

For Renovate that is right. It serves thousands of setups with very different rules. Making every skipped step visible would flood many dashboards. And whoever runs Renovate has the log, with every detail.

In my estate exactly that was not enough. With around 200 repositories nobody reads debug logs while nothing is red. And none of these cases turned a job red.

03 — The price

The opinion costs in three places.

More code in every stage. Every filter that holds an update must supply a reason. A new filter without a reason of its own cannot be built in. That makes every extension a little slower.

Longer plans. The plan also contains everything that does not happen. That is intended, but it makes the plan much larger than a list of open merge requests.

A dashboard that needs discipline. Not every reason is interesting to people. A lock file that is already current is not waiting, it is done. The dashboard has to leave such cases out, or it reports a problem every night where there is none.

04 — The occasion

In the old setup, three silent failures prompted this opinion. An automatic migration wrapped regexes twice, after which no custom manager ran. A group rule compared the wrong package name and never matched. A lock stood still for a year. None of them produced a red job. More in the post on the switch.

And then it hit pinup itself

On 25 September the updates for the PHP interpreter in my Wolfi packages did not come. This time I did not have to search. A warning in the plan named the reason in one line: the rule with allowedVersions held a range in npm notation, and pinup did not read it under semver.

That was a bug in pinup, not in the configuration. Renovate falls back to npm notation in this case; pinup did not. It was fixed the same day. The difference to the old setup: the bug was visible before anyone missed it.

The same case also showed a gap. When allowedVersions excluded every newer version, the plan reported the dependency as up to date. Since pinup 0.47 it carries a line like held by allowedVersions "/^8\.2\./" (packageRules[12]): 3 newer releases excluded, the newest 8.5.2 instead. It names the rule and the newest version behind the limit, and the dashboard shows it too.

Since pinup 0.48 this also holds next to an allowed update. The dashboard shows, after the update, which newer version the rule still holds back.

05 — The enforcement

A rule that lives only in the documentation does not hold for long. This one sits in three places in the code.

On top of that there is pinup whatif --now. It computes the plan for another moment. That is how I check whether a schedule window or a minimumReleaseAge really lifts when it is supposed to.

Frequently asked questions

Doesn't Renovate's dashboard show that too?+

Partly. Pending approvals, schedule windows and limits are there. Other things are only in the debug log, for instance a post-upgrade command that is not allowed. pinup holds such an update with a reason in the plan and on the dashboard.

How do I get the plan?+

pinup whatif --repo . --config .pinup.yaml writes it as JSON, without a token and without write access. With a renovate.json, pass --config renovate.json instead. pinup run stores the plan as an artefact. The dependency dashboard shows the held updates sorted by reason.

And what about versions that are never found at all?+

They are the limit of this opinion. A plan can only speak about dependencies a manager has read. For the rest there is the coverage scan. That is part 3 of the series.

Conclusion

An update that does not come needs a reason someone can read. That costs code, longer plans and a tidy dashboard. In return, failures show up before anyone misses them, including those in the tool itself.

Next week: checks that were never red, and why you should not trust them. The overview of all parts and the terms that recur are in the opening of the series.

Why is your update not coming? I will find the reason.

A plan over your repositories with pinup whatif, without write access: which updates are held, by which rule and until when.

Book a call →

About the author

Photo of Kai Ole Hartwig.

Kai Ole Hartwig

Freelance DevSecOps consultant · OnlyOle Consulting

Programming since 2002 – self-taught, set up my own business with KO-Web in 2012. Over 100 projects, with a focus on security, performance, automation and quality. Today freelance: DevSecOps consulting, training and software development.