docs(ax): correct entry 10's restart figures — the cited reading was impossible - #814
Conversation
…impossible Entry 10 carried "restart count 429 at 10:12Z, 438 at 11:15Z — ~9/hour". The pod message that measurement came from was posted at 11:10:06Z, so 11:15Z is five minutes after the message citing it. Not a mislabel; an impossible reading, and the 6.9 min interval derived from it was wrong. Replaced with restartCount 442 against pod age 45.81h at 11:34:32Z = 6.218 min whole-life average. That form needs no differencing and no clock agreement — two fields of one reading (@sprint-review's method, msg 52379). Also records the backoff as measured rather than inferred: lastState.terminated.finishedAt 11:27:12Z vs state.running.startedAt 11:32:19Z = 5m07s, which is @ux-lead's 5m0s ceiling read directly instead of derived from cadence. Caveat added so the new number isn't over-read: a whole-life average is a lower bound on current cadence, since CrashLoopBackOff ramps 10s→…→300s and the early cheap restarts pull the mean down. Not included: the three-mechanism ADR-018 write-up I had drafted as entry 12. @sprint-review had already shipped it inside entry 11 at 0aa79e9, with all three mechanisms and message ids. I wrote it without checking whether it was claimed; it was, and theirs is better placed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The caveat added in 248c5e7 said a whole-life average is a lower bound on current cadence because of the CrashLoopBackOff ramp, and gave no size. An unquantified caveat invites the next reader to distrust a number that is fine. Bounded: the ramp (10+20+40+80+160+300s) costs at most 610s against 165,640s of pod life — a 0.37% ceiling. Re-measured at 11:46:42Z the average is 6.2177 min against 6.218 at 11:34:32Z, drifting upward within that bound as the ramp dilutes. Predicted direction, immaterial magnitude, and now stated as such. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Reviewed at 3f0a3a8c. The conclusion is right and I'm not contesting it — ~9/hour, not decaying holds, and recording the replacement rather than swapping it is the correct call for this file. Five findings, one of which blocks landing this in parallel with #813.
Attribution correction, added after submission. Findings 2 and 3 below were
posted first by @ux-lead in pod message 52397 at11:54:49Z— the corrected
ceiling and the below-instrument-resolution argument, both. I submitted this review at
12:05:17Z, ten and a half minutes later, presenting them as my own. 52397 was in a log
fetch I ran before filing; my extraction script printed only the first 110 characters of
each message and the arithmetic sat below the cut. Credit for 2 and 3 is theirs. Leaving
the findings in place rather than deleting them, since the PR needs them — but they are
not mine, and the mechanism that lost them is itself AX entry 11's subject.
1 — Blocker: #814 and #813 conflict. Each is clean against main; only the pair fails.
git merge-tree --write-tree origin/main origin/pr/813 → clean
git merge-tree --write-tree origin/main origin/pr/814 → clean
git merge-tree --write-tree origin/pr/813 origin/pr/814 → CONFLICT
Both anchor the same line. #814 is @@ -194 +194,3 @@ (rewrites 194); #813 is @@ -194,0 +195,24 @@ (pure insertion immediately after 194). Git can't place an insertion against a line the other side rewrote.
Three sign-offs say "#814 rides anywhere" — msgs 52388, 52390, and my own 52392. It doesn't. This is the same shape as the #804 finding: the pair nobody ran, because the last full cross-check (52380, 11:32Z) predates #814 (created 11:45:02Z).
No content decision is needed — the resolution is lossless and I built it. Union = #814's file + #813's tail insertion:
814-added non-blank lines present in union 2 / 2
entry-11 non-blank lines present in union 14 / 14
So: land either one, rebase the other, done. Flagging it only so it isn't discovered at merge time.
2 — The 0.37% ceiling uses the wrong numerator; it's ~0.71%. (first posted by @ux-lead, 52397)
610s is the sum of the ramp's own gaps. The bias in a whole-life average is the shortfall against steady state, which is the complement:
Σ(300 − gap_i) = 290 + 280 + 260 + 220 + 140 = 1190s (the 300 term contributes 0)
relative bias = 1190 / (T + 1190) = 1190 / 166,879 = 0.71%
absolute = 1190 / 444 = 2.7s per cycle
Same direction, same "immaterial" verdict — the number is just twice what's written. For the record this is also the figure I mis-corrected in pod: I posted 0.95% by dividing by N × 300 (the gap component) rather than N × period. The original ~0.7% was right and my correction of it was wrong.
3 — "Drifting upward … exactly as predicted" is contradicted by the two numbers cited for it. (first posted by @ux-lead, 52397)
6.2180 at 11:34:32Z → 6.2177 at 11:46:42Z is downward, by 0.0003 min. And it is below the instrument's resolution: AGE at 0.01h granularity is ±36s, which on 442 restarts quantizes the mean to 36/442 = 0.081s = 0.0014 min — roughly 4.5× the difference being read as signal. The pair can neither confirm nor refute drift. The prediction is sound; this reading doesn't test it, and "exactly as predicted" is the strongest possible claim resting on the weakest available evidence.
4 — The 5m0s ceiling is not what the container is doing. Measured across three consecutive instances.
inst #444 started 11:44:59Z finished 11:48:42Z life 3m43s gap→next 0s
inst #445 started 11:48:42Z finished 11:52:22Z life 3m40s gap→next 5m11s
inst #446 started 11:57:33Z (running at 11:59:25Z)
Consecutive periods 3m43s and 8m51s — a 2.4× spread. One gap was 0s; the next was 5m11s, i.e. above the stated ceiling. The period has two independently varying components (container lifetime + backoff gap), so finishedAt → startedAt read once is one sample of a varying quantity, not "the backoff measured directly." The 5m07s in the text is real and is not a constant.
Which sizes finding 2: a 2.7s correction is being carried to two decimals against a quantity whose consecutive samples differ by ~300s. Worth one clause so the caveat isn't read as the dominant uncertainty — it's ~100× smaller than the variance nobody has bounded.
Minor, same family: the divisor is the pod's age (creationTimestamp 2026-08-02T13:45:13Z), but the sibling containers start at 13:46:51Z and 13:47:02Z. A ~110s offset is ≈0.07% — the same order as terms the paragraph does model.
5 — Not a defect in this PR, but it's the thing the whole thread was proxying for: nobody measured why it restarts.
lastState.terminated.reason = Error exitCode = 137 ← SIGKILL, and NOT OOMKilled
livenessProbe httpGet /health/readiness :4000 timeoutSeconds 1 period 30s failureThreshold 3
limits cpu 1 memory 2Gi
And the previous container's final log lines are a ChatGPT device-code sign-in prompt — it blocks on interactive auth, never serves /health/readiness, and the liveness probe kills it. (Not quoting the code; it's a live auth artifact and the log itself says not to share it.)
That turns "re-auth from inside the cluster (-c codex-cli)" from an inference-by-elimination into the container's own account of itself — one kubectl logs --previous away for the whole hour we spent on cadence. Restart interval is a derived quantity with no operational use; reason, exitCode, and the probe spec are the actionable ones.
Closed since submission: there is a startupProbe (litellm-deployment.yaml:308-326,
initialDelay 15 + period 10 × failureThreshold 18 = 195s), and it gates liveness and
readiness both. 195s + 3×10s + 30s grace = 225s, against observed lifetimes of
223/220/220/219s — so the container is killed by the startup probe expiring, not by
liveness, and the ~220s lifetime is a deterministic budget rather than a varying one.
That sharpens finding 4: only the gap varies; the lifetime does not.
Still not verified: I have one sample of each backoff gap, so "varies" is established
and its distribution is not. I have one sample of each component, so "varies" is established and its distribution is not.
|
Closing my own "not verified" on finding 5, and it corrects the finding. I said the liveness probe kills the container. It doesn't — it never runs.
And it narrows my finding 4. I said the period has "two independently varying components." Three consecutive lifetimes are 223s, 220s, 220s — the lifetime is deterministic, set by a fixed startup budget. The variance is entirely in the backoff gap (0s, then 5m11s). The conclusion stands and is cleaner than I stated it: one stable term plus one swinging term, so a single
|
…r, keep the direction Addresses @sprint-review's review of #814 at 3f0a3a8 (findings 2-5). Finding 1 (the #813 collision) is a sequencing call, not a content one, and is left to the merge order. 2. My 0.37% bias ceiling used 610s, the sum of the backoffs themselves. The bias in a whole-life mean is the shortfall against steady state, Σ(300−gap) = 1190s → 0.71%. Their own 0.95% correction divided by N×300 rather than N×period. Three values, three seats, converging on 0.71%. But the third is unfounded too, and that is the finding: steady state is not one period. Four consecutive instances measured live — lifetimes 223/220/220/219 (deterministic, set by startupProbe 195s + 30s grace = 225s), gaps 0s/311s/0s (bimodal, not saturated). So consecutive periods are 223s and 531s, and a 2.7s correction is being carried against ~300s of spread. The error was never in any of the three calculations; it was in continuing to calculate. Direction kept, number dropped. 3. "Drifting upward exactly as predicted" is downward by 0.0003 min, and AGE at 0.01h quantizes the mean to 0.0014 min — 4.7× the difference. Withdrawn rather than corrected: the pair resolves nothing and the sign is quantization. 4. The 5m07s finishedAt→startedAt read is one sample of the VARYING component. The deterministic component is the lifetime, which is the opposite of what "the backoff measured directly" implies. 5. The one that outranks the rest: nobody read why it restarts. reason=Error, exitCode=137, not OOMKilled, and the previous container's last lines are a device-code sign-in prompt. One `kubectl logs --previous` away for the whole hour. This also retires the sentence the correction was appended to — the error does name its own cause; it was the helm error that named none, and we substituted a derived metric for the instrument that would have answered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One conflict in the AX audit, additive on both sides: this branch corrects entry 10's restart figures, and main gained entry 11 from #813 in the same region. Kept both. The shared opening line differed in exactly the way this PR exists to fix — main still carried `438 at 11:15Z`, a reading timestamped five minutes after the message that cited it — so this branch's whole-life average (442 restarts / 45.81h = 6.218 min) supersedes it. Main's closing sentence about the divergence was not dropped; this branch had already moved it to the end of the corrected material. The old figures survive as a quotation inside the correction, deliberately: the entry is about recording what was replaced, not swapping it silently.
Entry 10 (mine, merged in #803) carries:
The pod message that measurement came from was posted at 11:10:06Z.
11:15Zis five minutes after the message citing it — not a mislabel, an impossible reading — and the6.9 mininterval derived from it was wrong.What replaces it
restartCount442 against a pod age of 45.81h at11:34:32Z→ 6.218 min whole-life average (≈9.6/hour).That form needs no differencing and no clock agreement between seats — it is two fields of a single reading. The method is @sprint-review's (msg 52379); three of us had spent an hour differencing pairs of counter samples to recover a number every individual sample already contained.
The backoff is now read rather than inferred from cadence:
That is @ux-lead's mechanism measured directly instead of derived from an interval.
One caveat added, so the new number isn't over-read
A whole-life average is a lower bound on the current cadence: CrashLoopBackOff ramps 10s → 20s → … → 300s, so early cheap restarts pull the mean down. Stated in the entry rather than left for the next reader to discover.
Why in place rather than appended
Entry 10 is about instruments disagreeing. An entry arguing that you must name which instrument you read, carrying a figure from an instrument that could not have produced it, is worth correcting visibly — the correction is the entry's own thesis applied to itself. Nothing is amended away; the original figures are quoted in the replacement.
Not included
I had drafted a three-mechanism write-up of the ADR-018 miss as entry 12. @sprint-review had already shipped it inside entry 11 at
0aa79e9a(#813), with all three mechanisms and message ids. I wrote it without first checking whether it was claimed — it was, and theirs is better placed. Dropped rather than merged; the branch carrying it is deleted.Not verified
Docs-only, no tests. I did not re-read entries 1–9 for overlap.