Skip to content

feat(local-collector): make doctor's verdict readable by a process supervisor - #84

Merged
tnunamak merged 2 commits into
mainfrom
feat/collector-doctor-exit-code
Sep 9, 2026
Merged

tnunamak merged 2 commits into
mainfrom
feat/collector-doctor-exit-code

Conversation

@tnunamak

@tnunamak tnunamak commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

A local collector — a one-shot program that uploads a machine's data on a timer, started by a process supervisor such as systemd — can be reporting that it is in trouble on every scheduled run without anything on the host noticing.

Its command-line tool already has a doctor command that checks four things about the machine it runs on: the local queue of records waiting to upload, records that failed repeatedly and were set aside for an operator, whether any queue entries are held by a worker that has gone away, and whether the server is reachable. It rolls those into one of ok, warning, or critical. But it printed that verdict inside a JSON document and always exited 0, and a process supervisor decides whether a program succeeded by reading its exit status alone. So the collector's own diagnosis was unreadable by the only thing watching it, unless a person ran the command by hand and read the output.

doctor --exit-code exits 1 when the rollup is critical and 0 otherwise. A supervisor can then act on it — the systemd documentation in this repository gains a section on wiring it to OnFailure=, a directive that runs another unit when one fails, to raise a local desktop notification.

Two choices worth explaining. The flag is opt-in rather than the new default because the existing contract is "print JSON on stdout, exit 0", and callers already parsing that output would suddenly see a failure status for a document they were reading successfully. And only critical exits non-zero: the warning conditions are ones the next scheduled run clears on its own — an expired queue lease, a missing database, a coverage gap — so treating them as failure would make a supervisor flap on states that are already resolving. That is the same reasoning the existing rollup uses to keep them out of critical.

This also renames one piece of console text. On a source's detail page each device carries a link to the device exporters page, and the link's visible text was the raw device identifier — a generated string with no meaning to a reader, which is worst exactly where the link matters most, next to a line saying the collector stopped checking in. It now reads "View this device", with the identifier moved to the tooltip and left in the URL fragment that scrolls the target page to the right row.

What this does not do

It does not detect a collector that has stopped running. OnFailure= requires the program to have run and exited non-zero, on a host whose service manager is up and whose desktop session is present to show a notification. It covers none of the cases — timer stopped firing, host powered off, person working elsewhere — where a collector goes quiet and only the server can notice. The documentation says so in place, because that limit is easy to mistake for a liveness answer. Server-side silence detection is separate work, in #74.

Verifying it

Eleven of eleven doctor tests pass in the local collector package, including three new ones: the flag is parsed and stays off by default, the severity mapping is pinned across all three values, and a real dead-lettered outbox produces a non-zero status. The mapping is a separate exported function rather than an inline comparison so it can be tested without invoking the command-line entry point.

Six tests parse the systemd unit documented in this repository and assert on the directives it declares; they still pass, so the new documentation section did not disturb the block they read. The console change is covered by that file's existing structural tests, which read the component as text and assert on its literals: 41 of 41 pass, and none pinned the previous link text.

Assisted-by: AI

The `doctor` command of the local collector's command-line tool already checks
four things about the machine it runs on — the local queue of records waiting to
upload, records that failed repeatedly and were set aside, whether any queue
entries are held by a worker that has gone away, and whether the server is
reachable — and rolls them into one of `ok`, `warning`, or `critical`. But it
printed that verdict inside a JSON document and always exited 0. A process
supervisor such as systemd decides whether a program succeeded by reading its
exit status alone, so nothing on the host could act on the collector's own
diagnosis without a JSON parser in between. In practice that means a collector
can be reporting `critical` on every scheduled run and no one finds out.

`doctor --exit-code` exits 1 when the rollup is `critical` and 0 otherwise. The
flag is opt-in rather than the new default because the existing contract is
"print JSON on stdout, exit 0", and callers that already parse that output would
start seeing a failure status for a document they were reading successfully.
`compact` in the same file established this idiom when it made a refused reclaim
exit non-zero, so the two scriptable commands now behave alike.

Only `critical` exits non-zero. The `warning` conditions are ones the next
scheduled run clears on its own — an expired queue lease, a missing database, a
coverage gap — so treating them as failure would make a supervisor flap on
states that are already resolving. That is the same reasoning the existing
rollup uses to keep them out of `critical`. The mapping is a separate exported
function rather than an inline comparison so a test can pin all three severities
without invoking the command-line entry point.

The systemd documentation gains a section on pairing this with `OnFailure=`, a
directive that runs another unit when one fails, to raise a local desktop
notification. It is labelled explicitly as a local-visibility aid and not a
liveness answer: `OnFailure=` requires the unit to have run and exited non-zero
on a host whose service manager is up and whose desktop session is present. It
covers none of the cases — timer stopped, host powered off, person elsewhere —
where the collector goes quiet and only a server can notice.

Verified with the package's own test runner: 11 of 11 doctor tests pass,
including three new ones covering the flag parsing, the severity mapping across
all three values, and a real dead-lettered outbox exiting non-zero. The six
tests that parse the documented systemd unit still pass, so the new
documentation section did not disturb the block they read.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
On a source's detail page, each device source instance carries a link to the
device exporters page — where an owner goes to see whether a collector is still
running. The link's visible text was the raw `device_id`, a generated identifier
with no meaning to a reader.

That is worst exactly where the link matters most. A reader who has just seen
the heartbeat line above it report a stale check-in has no way to tell that the
opaque string underneath is how to go look at the machine. The link now reads
"View this device". The device id moves to the link's tooltip and stays in the
URL fragment, which is what scrolls the target page to the right row, so nothing
about where the link goes changes.

Verified by running the file's existing structural tests, which read this
component as text and assert on the literals it contains: 41 of 41 pass. None
of them pinned the previous link text.

Assisted-by: AI
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
@tnunamak
tnunamak merged commit d2d52ea into main Sep 9, 2026
20 of 22 checks passed
@tnunamak
tnunamak deleted the feat/collector-doctor-exit-code branch September 9, 2026 18:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant