Chris Lorenzo

homelab and infrastructure notes · stayz3ro

entry-38

The update checker that succeeded without checking anything

An npm update checker exited successfully after skipping its work. Its systemd service lacked the NVM environment that made the same script work in a terminal.

revisions rev. 1 first set 2026-10-23 last revised 2026-10-23 2 min read

My npm update checker reported success on every run. The journal showed that it could not find Node and had skipped the check. The script still exited zero, so systemd had no failure to report.

I wanted weekly visibility into workstation updates without automatically installing anything. A system-level timer handled APT, and a separate user-level timer handled global npm packages. Both wrote their results to the journal. Package updates stayed a manual decision.

The corrected setup was deployed and validated by August 23, 2026.

It worked from my terminal

Node and npm were managed by NVM, the Node Version Manager. In my interactive shell, NVM initialization had already put the selected runtime on PATH. Manually launching the checker from that environment appeared to work.

The systemd user service did not have that environment. It launched the checker without the NVM-managed runtime on its PATH. The script treated the missing dependency as a reason to skip its work, then returned success.

That made the failure easy to miss. The scheduled process ran and exited cleanly, but the outdated-package check never happened. A successful manual test had also depended on shell state that the service did not provide.

Make the service prepare its own runtime

I changed the checker invoked by the service to initialize NVM explicitly and select its default Node version. It then checked that both Node and npm were available before doing any package reporting.

Missing NVM initialization, a failure to activate the runtime, or a missing Node or npm executable now produced an explicit error in the journal and a nonzero exit. Those conditions could no longer pass as a completed check.

The fix stayed inside the checker. It no longer relied on an interactive shell having prepared the environment first. The timers still only reported available updates; neither installed or upgraded packages.

Read what the check actually did

Validation included the service result, but it did not stop there. I read the journal for evidence that NVM initialized, Node and npm resolved, and the package inventory and outdated-package detection steps actually ran. The corrected run reported an outdated package and left it untouched.

The timers were enabled and active. The services were oneshot jobs, so being inactive after a successful run was expected. Neither an active timer nor a successful service exit proved that the package check had executed. The journal content supplied that missing evidence.

A successful process exit is not proof that the intended check actually ran. In this case, the script itself had turned a missing dependency into success. Explicit runtime setup fixed the environment problem, and a nonzero exit made a missing runtime visible as a failure.

The same trap applies to a service that relies on shell-only setup from rbenv or another version manager. Test through the service’s actual environment, then inspect the output for the work it was meant to do.