Skip to content

docs(ax): correct entry 10's restart figures — the cited reading was impossible - #814

Merged
lilyshen0722 merged 4 commits into
mainfrom
docs/ax-entry10-restart-figures
Aug 4, 2026
Merged

docs(ax): correct entry 10's restart figures — the cited reading was impossible#814
lilyshen0722 merged 4 commits into
mainfrom
docs/ax-entry10-restart-figures

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

Entry 10 (mine, merged in #803) carries:

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. 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.

What replaces it

restartCount 442 against a pod age of 45.81h at 11:34:32Z6.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:

lastState.terminated.finishedAt   2026-08-04T11:27:12Z
state.running.startedAt           2026-08-04T11:32:19Z
                                  → 5m07s, the CrashLoopBackOff 5m0s ceiling

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.

lilyshen0722 and others added 2 commits August 4, 2026 04:42
…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>

@lilyshen0722 lilyshen0722 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 at 11: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:32Z6.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.

@lilyshen0722

Copy link
Copy Markdown
Contributor Author

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.

litellm-deployment.yaml:308  startupProbe    initialDelay 15 + period 10 × threshold 18  =  195s
                        :315  readinessProbe  period 10 × threshold 3
                        :321  livenessProbe   period 30 × threshold 3
terminationGracePeriodSeconds  30  (live pod; not set in the chart)

budget    195 + 30  =  225s
observed  #444 223s · #445 220s · #446 220s

startupProbe gates both liveness and readiness in Kubernetes, so the ~120s liveness path I quoted is the one never taken — which is exactly why the lifetime is ~220s. Anyone tuning the liveness probe to stop this would change nothing. Credit @ux-lead (msg 52408); arithmetic and the grace period re-verified here.

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 finishedAt → startedAt read still samples a varying quantity, and the whole-life mean is still the better instrument.

exit 137 after a full 30s grace also corroborates the device-code finding — a process that can't handle SIGTERM is what blocked-on-an-interactive-prompt looks like, and the readiness probe on the same path is why the pod reads 2/3.

…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.
@lilyshen0722
lilyshen0722 merged commit 7d797cb into main Aug 4, 2026
10 checks passed
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