Replacing prometheus's laptop cell on 2026-09-18
(#454) disconnected the battery
that backs the RTC. The machine came back with no idea what time it was, wrote
about fourteen minutes of Prometheus samples at the wrong timestamps, and
nothing in the estate noticed — including the rule written for clock
problems.
What happened, from the host's own journal and TSDB
| Real time |
What the machine believed |
What was recorded |
| 14:53:37 |
14:53:37 |
clean shutdown, last good samples |
| ~17:58 |
2026-07-28 15:04:45 |
post-swap boot; journal boot -1 opens here |
| ~17:58 |
stepped to 14:53:37 |
systemd-timesyncd: "System clock time unset or jumped backwards, restored from recorded timestamp" |
| ~17:58–18:12 |
14:54:30–15:08 |
~14 min of real operation, written backdated |
| 18:12 |
18:12 |
NTP syncs, clock steps forward ~3 h 4 m |
systemd-timesyncd restores the clock from its own clock file, whose timestamp
is the last time it ran — the second the machine went down. So the post-swap
boot is not merely wrong, it is wrong in a way that lands inside the outage it
is meant to be after.
Why this matters more than a cosmetic timestamp
- The TSDB shows a 184-minute hole at 15:08–18:12 that is not downtime. The
true window was 14:53:37 to about 18:12, roughly 3 h 18 m. Neither figure is
recoverable from the series: one is an artefact of the step, the other was
never recorded at all.
- Real post-swap operation is filed inside the outage.
HostOnBattery
genuinely fired for prometheus — the machine came back unplugged — and it is
stamped 14:56 pending, 14:57 firing, which is three hours before it happened
and inside a window the series otherwise reads as dead.
replace-the-laptop-cell.md's step 6 tells the operator to measure the
outage with query_range. After a battery swap that instruction returns a
confident wrong answer, which is the failure mode this repository keeps
warning about one level up. That runbook is being corrected to say so; this
issue is about whether anything should detect it.
oracle is next in line for the same procedure and will do the same
thing.
HostClockSkew is blind to this, and the reason is structural
host.rules.yaml:74 —
- alert: HostClockSkew
expr: abs(node_timex_offset_seconds) > 0.5
for: 10m
node_timex_offset_seconds peaked at 0.004 s across the whole event, and
HostClockSkew has never fired in the 30-day retention. That is not bad luck.
The metric is the kernel PLL's current correction estimate; once timesyncd has
restored a clock that is stable and three hours wrong, there is nothing to
correct and the offset is tiny. The rule detects a clock that is drifting, not
one that is wrong.
The signal that was there all along is one field over:
node_timex_sync_status{instance="prometheus"}
It went to 0 at 14:55 and back to 1 at 18:12 — precisely the backdated
window. Nothing in stacks/observability/prometheus/rules/ reads it.
The question
Should something notice, and what should it read? Candidates, none of them yet
argued for:
node_timex_sync_status == 0 for some for: — exactly what went unread here,
and it would also catch an NTP outage that has not yet produced drift. It will
fire briefly on every ordinary boot, so the for: is the whole design.
- A backward jump in
node_boot_time_seconds, which would catch this case
specifically but is a narrower claim.
- Nothing, on the grounds that a battery swap is a supervised operation and the
runbook now says to read the journal. That is a legitimate answer and should
be written down if it is the one chosen.
There is a real cost to (1) and (2): the monitoring host writing its own
timestamps means Prometheus may be down for the interesting part, which is what
happened here. Whatever is chosen should say what it can and cannot see.
Not in scope of the change that raised this
No rule lands with the #454 fit write-up. That change corrects
replace-the-laptop-cell.md so the operator takes the window from
journalctl --list-boots rather than query_range, and points here.
🤖 Generated with Claude Code
Replacing
prometheus's laptop cell on 2026-09-18(#454) disconnected the battery
that backs the RTC. The machine came back with no idea what time it was, wrote
about fourteen minutes of Prometheus samples at the wrong timestamps, and
nothing in the estate noticed — including the rule written for clock
problems.
What happened, from the host's own journal and TSDB
2026-07-28 15:04:45-1opens here14:53:37systemd-timesyncd: "System clock time unset or jumped backwards, restored from recorded timestamp"systemd-timesyncdrestores the clock from its own clock file, whose timestampis the last time it ran — the second the machine went down. So the post-swap
boot is not merely wrong, it is wrong in a way that lands inside the outage it
is meant to be after.
Why this matters more than a cosmetic timestamp
true window was 14:53:37 to about 18:12, roughly 3 h 18 m. Neither figure is
recoverable from the series: one is an artefact of the step, the other was
never recorded at all.
HostOnBatterygenuinely fired for
prometheus— the machine came back unplugged — and it isstamped 14:56 pending, 14:57 firing, which is three hours before it happened
and inside a window the series otherwise reads as dead.
replace-the-laptop-cell.md's step 6 tells the operator to measure theoutage with
query_range. After a battery swap that instruction returns aconfident wrong answer, which is the failure mode this repository keeps
warning about one level up. That runbook is being corrected to say so; this
issue is about whether anything should detect it.
oracleis next in line for the same procedure and will do the samething.
HostClockSkewis blind to this, and the reason is structuralhost.rules.yaml:74—node_timex_offset_secondspeaked at 0.004 s across the whole event, andHostClockSkewhas never fired in the 30-day retention. That is not bad luck.The metric is the kernel PLL's current correction estimate; once timesyncd has
restored a clock that is stable and three hours wrong, there is nothing to
correct and the offset is tiny. The rule detects a clock that is drifting, not
one that is wrong.
The signal that was there all along is one field over:
It went to 0 at 14:55 and back to 1 at 18:12 — precisely the backdated
window. Nothing in
stacks/observability/prometheus/rules/reads it.The question
Should something notice, and what should it read? Candidates, none of them yet
argued for:
node_timex_sync_status == 0for somefor:— exactly what went unread here,and it would also catch an NTP outage that has not yet produced drift. It will
fire briefly on every ordinary boot, so the
for:is the whole design.node_boot_time_seconds, which would catch this casespecifically but is a narrower claim.
runbook now says to read the journal. That is a legitimate answer and should
be written down if it is the one chosen.
There is a real cost to (1) and (2): the monitoring host writing its own
timestamps means Prometheus may be down for the interesting part, which is what
happened here. Whatever is chosen should say what it can and cannot see.
Not in scope of the change that raised this
No rule lands with the #454 fit write-up. That change corrects
replace-the-laptop-cell.mdso the operator takes the window fromjournalctl --list-bootsrather thanquery_range, and points here.🤖 Generated with Claude Code