Skip to content

fix(doctor): node-fit checks FREE memory, not allocatable (backend#2870) - #628

Merged
LukasWodka merged 9 commits into
developfrom
fix/2870-doctor-node-fit-free-memory
Sep 2, 2026
Merged

fix(doctor): node-fit checks FREE memory, not allocatable (backend#2870)#628
LukasWodka merged 9 commits into
developfrom
fix/2870-doctor-node-fit-free-memory

Conversation

@LukasWodka

@LukasWodka LukasWodka commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Part of tracebloc/backend#2870 — the cli-doctor layer (DoD item 3).

The bug

checkNodeFit (the "Node capacity" check) fit the training envelope against each Ready node's Allocatable, never against what is already requested on it. So a node whose own control plane claims memory passed the check — allocatable said "a Ready node can schedule a training job" — while the pod then went Pending / Insufficient memory. The ticket's framing: no layer owned the question "is this number schedulable here?", and the one value that would expose it — free memory — was computed elsewhere and thrown away.

The fix

Sum the requests already on each Ready node and fit cpu + memory against FREE (allocatable − requested, floored at 0):

  • A node large enough by allocatable but not beside its own control plane now FAILs, with a message that names the over-commit — distinct from "no node is big enough at all".
  • Fail-closed: if the pod list can't be read, free is unknown — the check says so and falls back to allocatable rather than passing it off as free.
  • Terminal pods (Succeeded/Failed) hold nothing and are skipped (mirrors requestedMemory).

Verification

  • go test ./internal/doctor/ ./internal/cli/ — green. New TestCheckNodeFitFreeMemory: a 16Gi node whose control plane requests 12Gi (4Gi free) refuses an 8Gi envelope; the same node without the neighbour passes (the subtraction is load-bearing); a terminal neighbour is ignored.
  • Mutation-proven: dropping the memory subtraction flips the over-commit case to a false OK.
  • gofmt/go vet clean. The copy-catalog golden was regenerated — it added only the two new over-commit strings.

Scope (what remains on backend#2870)

This is one of five DoD items. Still open: the installer-side refusal (1), the footprint schedulability test tied to the #2460 overhead constant (2 — currently red-by-construction until that lands), the jobs-manager permanent-vs-transient waiting_for_capacity message (4), and the CPU envelope audit (5). This PR makes the class detectable at the diagnostic layer without depending on the #2460 measurement.

🤖 Generated with Claude Code


Note

Medium Risk
Changes doctor readiness verdicts, exit semantics, and remediation text; cluster-wide pod listing adds an RBAC dependency. Behavior is heavily tested but rollup ordering is subtle.

Overview
tracebloc doctor now judges whether a training job can schedule using free CPU/memory on each node (allocatable minus steady-state pod requests), not raw allocatable. That surfaces over-committed machines where the control plane already consumes the headroom and jobs stay Pending despite looking “big enough.”

The Node capacity check lists pods cluster-wide (skipping terminal and batch Job pods), adds a distinct Fail with its own remedy, and warns instead of passing when the pod list is unreadable. The drift nudge caps suggested run sizes by free memory and names resources set --cores/--memory when set max would disagree with the printed ceiling.

summarizeDoctor adds a rollup arm for the over-commit Fail (explicitly not recommending resources set max) and places it above stuck-Pending pod warnings so co-occurring symptoms get the right top-line advice. Shared prefixes OverCommitted and CantVerifyFreeCompute keep classification in sync. Version 0.10.22; tests and copy golden updated.

Reviewed by Cursor Bugbot for commit c3d8d4d. Bugbot is set up for automated code reviews on this repo. Configure here.

…-committed control plane is caught (backend#2870)

`checkNodeFit` fit the training envelope against each node' Allocatable and
never against what is already requested on it. So a node whose control
plane claims memory passed -- allocatable said "big enough" -- while the
pod went Pending / Insufficient memory. This is the layer the ticket says
owns nothing: the installer sizes from allocatable, cli doctor checked
against allocatable, and the one number that would expose it (free memory)
was computed and thrown away.

Sum the requests already on each Ready node and fit cpu+memory against FREE
(allocatable - requested, floored at 0). A node large enough by allocatable
but not beside its own control plane now FAILS with a message that names the
over-commit, distinct from "no node is big enough at all". Fail-closed: if
the pod list cannot be read, free is UNKNOWN -- say so and fall back to
allocatable rather than passing it off as free. Terminal pods hold nothing
and are skipped (mirrors requestedMemory).

Scope: this is DoD item 3 of backend#2870 (the cli-doctor layer). The
installer-side refusal (1), the footprint schedulability test tied to the
#2460 constant (2), the jobs-manager permanent-vs-transient message (4) and
the CPU envelope audit (5) remain.

Test: a 16Gi node whose control plane requests 12Gi (4Gi free) refuses an
8Gi envelope; the same node without the neighbour passes (the subtraction is
load-bearing); a terminal neighbour is ignored. Mutation-proven: dropping the
memory subtraction flips the over-commit case to a false OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@LukasWodka LukasWodka self-assigned this Sep 2, 2026
@LukasWodka

Copy link
Copy Markdown
Contributor Author

bugbot run

Comment thread internal/doctor/doctor.go
Comment thread internal/doctor/doctor.go
Comment thread internal/doctor/doctor.go
v0.10.21 is already released and this PR changes published files under
internal/*; the version-bump-gate requires the develop VERSION to lead the
released tag so the next train hop cuts an unreleased version.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@saqlainsyed007 saqlainsyed007 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Holding — CI is red (bugbot / review failing) and three Bugbot threads are open, so this isn't landable yet. I verified the top one against the code rather than taking it on trust:

"Doctor fails during healthy training" (High) is real. checkNodeFit sums requests from every non-terminal, node-assigned pod — which includes a currently running training job that already holds the envelope — then tests whether a fresh cpuReq/memReq fits in the remainder. On a single-node install with a healthy training job running, freeMemB = allocatable − (that job's request) is below the envelope, so nodeCPUMem goes false, the node reports as not fitting, and doctor exits 2 while training is fine. That's a false negative on the exact healthy state doctor is meant to bless.

The other two are worth a look while you're in there:

  • "Unknown free reported as schedulable" (High): the code comment says the unknown-free caveat "is reported below" — please confirm the StatusOK path actually emits it when freeKnown == false, since that's precisely what the finding says is missing.
  • "Over-commit message always blames memory" (Medium): the over-commit copy naming FREE memory unconditionally will misreport a CPU or ephemeral-storage shortfall.

Requesting changes; happy to re-review as soon as these are addressed and CI is green.

…free, name the short dimension (backend#2870)

Three findings on the first cut (Saqlain + Bugbot):

- HIGH: the free sum counted every non-terminal pod INCLUDING a running
  training job that already holds the envelope, so on a single-node install
  doctor returned StatusFail and exited 2 during HEALTHY training -- a false
  negative on the state it exists to bless. Skip batch-Job pods (the job-name
  label the batch/v1 controller stamps); the envelope must fit beside the
  STEADY-STATE control plane, not a transient workload.
- HIGH: when the pod list could not be read the OK path returned StatusOK
  with no caveat, asserting free schedulability it never verified. Now WARN
  with an allocatable-only caveat.
- MEDIUM: the over-commit message always blamed FREE memory; it now names the
  actual short dimension (cpu / memory / both).

Tests: a running job labelled job-name is excluded (healthy training stays
ok); an unreadable pod list warns; a cpu over-commit names cpu. All
mutation-adjacent and green; golden updated for the new copy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
Contributor Author

bugbot run

Comment thread internal/doctor/doctor.go
Comment thread internal/doctor/doctor.go

@saqlainsyed007 saqlainsyed007 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the fast turnaround — my three original findings are genuinely addressed on this commit: running jobs are excluded from the free sum via the job-name label (no more false-fail during healthy training), unknown-free now returns StatusWarn rather than a clean OK, and the over-commit message tracks overCPU/overMem.

Still holding, because Bugbot's pass on the new commit caught two real follow-ons to those same two fixes — and I've confirmed both against the code:

  • Disk shortfall still blamed on memory (doctor.go:769): the over-commit arm fires on !cpuMemFits && allocOnlyFit, but allocOnlyFit only inspects allocatable cpu+memory — a node that fits cpu+memory and fails only on ephemeral-storage still takes this arm, and overCPU/overMem are both false, so short defaults to "memory". The message fix covered cpu but not the disk dimension — the same misdiagnosis class TestCheckNodeFitDisk exists to prevent.
  • Rollup still greens unverified free capacity (doctor.go:818): the new StatusWarn detail begins a Ready node can schedule a training job, but summarizeDoctor only treats the two older Node-capacity Warn prefixes as a can't-check — so it falls through to "Ready to run training" and prints "Everything looks good" at exit 0 on the default path. The Warn is there but the rollup doesn't act on it, so the over-committed control plane this PR exists to surface stays invisible without --verbose.

Fold the ephemeral-storage dimension into the short-dimension logic, and teach summarizeDoctor to treat the new Warn prefix as a can't-check, and this is good to go. CI is also red on bugbot / review for these.

…blame disk shortfalls on memory (backend#2870)

Two follow-on findings from the free-memory fix:

- HIGH: the unknown-free StatusWarn detail started with "a Ready node can
  schedule...", which summarizeDoctor does not recognise as a can't-check,
  so the rollup fell through to "Ready to run training" at exit 0 -- the
  over-commit stayed invisible on the default path. Give the warn a distinct
  "could not read the pod list" prefix and teach the rollup to treat it as a
  can't-check (Unknown), beside the RESOURCE_REQUESTS / nodes-unlistable ones.
- MEDIUM: the over-commit arm fired on !cpuMemFits && allocOnlyFit, which a
  DISK-only shortfall also satisfies (allocOnlyFit is cpu+mem only), so a disk
  short was reported as a FREE-memory over-commit. Guard the arm with
  (overCPU || overMem); a disk short now falls through to the generic fail that
  names ephemeral-storage.

Tests: disk-only shortfall is not blamed on memory; the unknown-free warn
rolls up to "couldnt check free compute", not Ready. golden updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
Contributor Author

bugbot run

Comment thread internal/doctor/doctor.go
…kend#2870)

Bugbot High. `summarizeDoctor` classifies a Node-capacity Warn as a can't-check
by PREFIX, and the `gpuRequested && !fullFits` case sits ABOVE the `!freeKnown`
branch with a detail carrying no such prefix. So with a GPU requested and the pod
list unreadable, the soft GPU Warn won, the can't-check was never emitted, and
the rollup fell through to "Ready to run training" at exit 0 -- a clean bill over
a cluster whose free compute doctor had not looked at.

Not an exotic path: the chart stamps `nvidia.com/gpu` on CPU-only installs, so
`gpuRequested` is commonly true where no node exposes a GPU, which IS this case.

The GPU fallback stays soft and stays reported; it just no longer suppresses the
stronger statement. Both facts ride in one Warn, can't-check FIRST because the
rollup matches on the prefix.

AND THE PREFIX IS NOW DEFINED ONCE. It was written out three times -- producer,
classifier, and the classifier's test -- which is a check holding its own copy of
the rule it verifies. `doctor.CantVerifyFreeCompute` is the single definition;
internal/cli already imports internal/doctor, so the classifier reads it rather
than retyping it, and the test builds its fixture from it. A producer that
rewords the phrase now moves all three together instead of leaving a test green
against a string nothing emits.

Three guards, two of them new:
  - unknown free AND gpu requested -> asserts the detail STARTS with the
    constant (Contains would pass on a detail that merely mentions it somewhere
    and still greens the run), and that the GPU fact survives.
  - gpu requested with free KNOWN -> asserts NO can't-check prefix, so the fix
    cannot be read as "always warn about free".
  - the classifier's can't-check table gains the COMBINED detail, which is what
    a GPU-requesting install with an unreadable pod list actually produces.

Mutation-proved: deleting the new `!freeKnown` branch reddens the first test with
the pre-fix detail verbatim.

Verified: `go build ./...`, `go vet ./...` clean, `go test ./...` all packages ok.
`zz-all-strings.golden` regenerated for the two new user-facing strings --
additions only, no deletions and no orphaned literal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
Contributor Author

bugbot run

Comment thread internal/cli/doctor.go
…machine (backend#2870)

Bugbot Medium, and the advice was not merely vague -- it was the opposite of the
fix. `computeRemedy` ends every variant with "size runs to this machine with
`resources set max`", and `set max` measures the machine's TOTAL, which is the
figure the #2870 Fail just rejected. A user who follows the top line asks for
MORE and the training stays stuck; the accurate wording was only behind
`--verbose`.

The two Node-capacity Fails need opposite advice and were sharing one rollup arm:
  - "no Ready node can fit" -> the machine is too small; sizing runs to it, or
    giving it more, is right.
  - "a Ready node IS large enough, but not beside what is already on it" -> the
    machine is fine; asking for more is what breaks it.

So the over-commit Fail gets its own arm, keyed on a new `doctor.OverCommitted`
prefix that the producer now composes its detail FROM -- the same one-definition
treatment `CantVerifyFreeCompute` got in the previous commit, and for the same
reason: this classification is by prefix, so a reworded producer would silently
fall back to the generic arm and restore the bug.

Plain terms, no Kubernetes vocabulary, like its two neighbours --
`renderDoctorDetails` is documented as the only place that appears, and the
granular Remedy one `--verbose` away already names the knob.

Two tests, both directions, because a one-sided fix here is easy to get wrong:
  - over-commit -> must NOT contain "resources set max", must warn against it.
    Its fixture detail is BUILT from `doctor.OverCommitted` so arm and producer
    cannot drift.
  - generic too-small Fail -> must STILL offer the sizing fix, so this cannot
    strip correct advice from the case it is correct for.

Mutation-proved: deleting the new arm reddens the first test with the generic
remedy verbatim ("... or size runs to this machine with `tracebloc resources set
max`.").

Verified: `go build`, `go vet` clean, `go test ./...` all packages ok.
`zz-all-strings.golden` regenerated -- three new strings, one replaced by its
constant-composed form.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
Contributor Author

bugbot run

Comment thread internal/doctor/doctor.go

@saqlainsyed007 saqlainsyed007 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Good progress — everything from my earlier passes is genuinely in: running jobs excluded from the free sum, unknown-free warns (and now, in this commit, that can't-check correctly OUTRANKS the soft GPU warn via the CantVerifyFreeCompute prefix so summarizeDoctor no longer greens it), disk-vs-memory shortfall named, and the shared OverCommitted/CantVerifyFreeCompute prefixes tidy that up nicely.

One open item, and I confirmed it against the code — Bugbot's "OK nudge still quotes allocatable max" (doctor.go:832) is real: the fit now moves to FREE, but the StatusOK path still appends the resources set max nudge computed from allocatable-minus-overhead (maxCores/maxGiB). On a node that's large enough but already partly claimed, that advertised ceiling can exceed free — so an operator who follows "size up to cpu=…,memory=…Gi" recreates exactly the over-commit this check now fails on, and the next doctor run tells them not to size to the machine. The nudge should be bounded by free (or suppressed when free < the allocatable-derived max) so the advice can't contradict the verdict. Once that's addressed it's good to go — CI is green and every other thread is resolved.

…end#2870)

Bugbot Medium, confirmed by @saqlainsyed007 against the code: the fit moved to
FREE and the `resources set max` nudge stayed on allocatable-minus-overhead.
`bestCPU`/`bestMem` are the largest ALLOCATABLE across Ready nodes; the verdict
beside them is computed from per-node free. On a node that is big enough and
already partly claimed those disagree, and the nudge won -- so an operator who
followed "this machine could give a run up to cpu=...,memory=...Gi" recreated
the exact over-commit this check now fails on, and the next `doctor` run told
them not to size to their own machine.

Advice that contradicts the verdict printed beside it is worse than no advice,
so the ceiling is now bounded by free: the largest FREE cpu/memory on any one
Ready node is tracked alongside the allocatable-derived best, and the machine
handed to `MaxRunCores`/`MaxRunGiB` is the smaller of the two.

When free is UNKNOWN the nudge is suppressed entirely rather than falling back
to allocatable. There is nothing to bound it with, and that arm is already a
Warn saying free could not be verified -- emitting a confident ceiling from the
number we just said we could not trust is the same defect one step along.

Test: a 32/64Gi node with 24 cores + 40Gi held by a non-job pod. The run
(2/8Gi) still fits the free 8/24Gi so the result stays OK, and the detail must
not advertise the allocatable ceiling `cpu=31,memory=61Gi`. Mutation-proved:
forcing the bound off reddens exactly that test and nothing else; restored, the
suite is green.

go build, go vet, gofmt: clean. `go test ./...`: all packages ok.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
Contributor Author

bugbot run

Comment thread internal/doctor/doctor.go

@saqlainsyed007 saqlainsyed007 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

My own asks are all addressed now — the resize nudge is bounded by free and suppressed when free is unknown, exactly right, and I'm satisfied with that.

The one thing still blocking is Bugbot's new finding (doctor.go:861, "Nudge ceiling diverges from set max"), and I think it's a fair one: the nudge now prints a free-bounded ceiling, but the command it points the operator at — resources set max — sizes from allocatable via nodeLarger, so on a claimed node the number shown and the number set max would actually apply disagree, and following the parenthetical reapplies the over-commit this check now refuses. Either recompute the advertised ceiling the same way set max will (or teach set max/nodeLarger to bound by free too), or drop the resources set max pointer from the free-bounded nudge so the display and the command can't contradict. If you consider set max's allocatable sizing out of scope for this PR, say so on the Bugbot thread and we can treat it as a follow-up — either way that thread needs closing before this can land. Everything else is green and resolved.

…bers (backend#2870)

Bugbot Medium on db56239, and it is the second half of my own last fix. Bounding
the ceiling by free corrected the FIGURE and left the ATTRIBUTION behind:
`tracebloc resources set max` sizes from allocatable via `LargestReadyNode`, so
on a claimed node it applies more than the number printed beside it. An operator
who read "up to cpu=7,memory=21Gi" and ran the command it named got the
allocatable ceiling instead -- reapplying the over-commit this check had just
started refusing. Same defect as the finding before it, one step along: the
verdict and the advice beside it disagreed.

`set max` is now named only when it would genuinely land on the printed numbers
-- when nothing meaningful is claimed and the free-bounded ceiling equals the
allocatable one. Otherwise the nudge names the explicit form,
`tracebloc resources set --cores N --memory NGi`, which applies exactly what it
printed. The drift signal survives on a busy node instead of being suppressed,
and it stays truthful.

The existing test asserted the FIGURE only, which is why a wrong command could
sit beside a right number and stay green. It now also asserts that a claimed
node does not name `set max` and does name the explicit form. Mutation-proved
with a mutation that COMPILES -- my first attempt left `allocM` unused, and a
build failure is not a caught mutation, it is an invalid one. Relaxing the
equality to `<=` (always true for a free-bounded machine) makes the nudge say
`set max` again and reddens exactly that assertion.

`internal/cli`'s zz-all-strings golden is regenerated: the two format strings
this changes, and nothing else (`git diff --numstat` = 2 added, 1 removed).

go build, go vet, gofmt: clean. `go test ./...`: 18 packages ok.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
Contributor Author

bugbot run

Comment thread internal/cli/doctor.go Outdated
…auses (backend#2870)

The previous commit fixed the FIGURE the nudge prints and left the ROLLUP
still recommending the thing. summarizeDoctor consulted the Pod-health
stuck-Pending arm before the over-commit arm, and that arm ends in
computeRemedy -- "size runs to this machine with `resources set max`" --
which sizes from allocatable, the number the over-commit Fail just
rejected. Following it raises the ask and leaves the job stuck.

The two states CO-OCCUR BY CONSTRUCTION, which is what makes this a
correctness bug rather than a preference about ordering: the producer's own
Detail ends "so the pod schedules Pending" (internal/doctor/doctor.go:778),
so an over-committed node is EXPECTED to also have pods stuck Pending. The
over-commit arm was therefore close to unreachable in the field.

Moved above the stuck-Pending WARN and no further: a hard Pod-health Fail
is a different problem with a different fix (reinstall) and is not caused
by this one, so it still wins. Both directions are asserted.

The existing over-commit test could not have caught this -- it builds its
fixture from allOK, so Pod health is OK and the over-commit arm wins either
way. The new test constructs the state where both fire, which is the state
the producer says is the normal one.

Verified: go test ./... -> 18 packages ok, 0 failed; gofmt clean; go vet
clean. Mutation-proved -- putting the stuck-Pending arm back on top reddens
the new case with the real `set max` remedy in the message, and reddens
ONLY the new case, which is the coverage gap Bugbot found.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit c3d8d4d. Configure here.

@saqlainsyed007 saqlainsyed007 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approving — this supersedes my earlier change-request. The Bugbot precedence finding is genuinely fixed, not papered over, and I verified the fix and its tests against the code.

The reorder is the correct fix: the over-commit Node capacity Fail arm now sits ABOVE the stuck-Pending Pod health Warn arm. That ordering is a correctness requirement, not a preference — the two states co-occur by construction (the over-commit producer's own Detail ends "so the pod schedules Pending", internal/doctor/doctor.go:778), so an over-committed node is expected to also show a stuck-Pending pod. With the old ordering the stuck-Pending arm matched first and printed computeRemedyresources set max, putting the exact remedy this Fail exists to refuse back in front of the operator. The earlier commit fixed the figure; this fixes the rollup that was still recommending it. Only a hard Pod health Fail (pods not running at all — a different, reinstall-class problem, not caused by over-commit) still outranks it, which is right.

The two added tests pin both directions and are mutation-proof:

  • "over-commit outranks stuck-Pending, which it CAUSES" sets BOTH Node capacity=Fail(OverCommitted) and Pod health=Warn(stuck Pending) and asserts the remedy has no resources set max, carries the over-commit top line ("already claimed the room"), and the "Do NOT" wording — so a regression to the old ordering reddens it.
  • "a hard Pod-health Fail still outranks over-commit" is the bound: it proves the reorder didn't slide up past a harder failure.
    Both build their details from the producer's own doctor.OverCommitted constant rather than retyping, so a reworded producer reddens them instead of silently falling through.

CI green (28 pass), no open threads.

@LukasWodka
LukasWodka merged commit 6aaa062 into develop Sep 2, 2026
33 checks passed
@LukasWodka
LukasWodka deleted the fix/2870-doctor-node-fit-free-memory branch September 2, 2026 10:04
@LukasWodka

Copy link
Copy Markdown
Contributor Author

/fr-pass

Functional review on staging — passed.

Tier-A journey against staging (this hop deployed), run 33724330593: install → cluster-anchor → ingest (15/16 task types) → use case → train → leaderboard (accuracy=0.9, row on the board). This repo's change is on that runtime path, so this is direct functional evidence.

Two caveats, stated rather than glossed:

  • object_detection ingest is mid-migration and NOT covered here. data-ingestors#552 (Phase 1 of backend#1006) now enumerates Pascal-VOC XML and rejects the legacy labels.csv; cli (Phase 2) / backend (Phase 3) are pending. The journey's object_detection path fails on that intended change, so it is not part of this evidence.
  • No KMS authorization. This hop carries STAGE 1.5 (read-decrypt-always) with prod values at null; the STAGE-2 flip is reverted/parked. Ready for prod here does not authorize the weight-store KMS flip, which stays gated on STAGE 1.5's prod deploy.

@cursor cursor Bot mentioned this pull request Sep 3, 2026
LukasWodka added a commit that referenced this pull request Sep 5, 2026
…ot enough compute" (backend#2870)

Bugbot High on #639: the transient shortage's own symptom is a second
training pod Pending until the running job frees the room, and the
stuck-Pending Fail arm matched it first -- "Not ready ... not enough free
compute" with `resources set max` at exit 2, in exactly the case the
HeldByRunningJob Warn was written for.

A combined arm now sits above the stuck-Pending arm: when Pod health reports
a pod Pending past grace AND Node capacity has measured that a running job
holds the room, the top line says the next training is waiting for the
running one, the remedy says wait or stop the job and that resizing will
not help, and -- since checkPods cannot see WHY a pod is Pending -- what to
do if the wait outlives the job. Warn, not Fail: the inference "Pending so
cannot schedule" is refuted by the measured cause, and a Fail would exit 2
on healthy training (the Bugbot High on #628). A Pod-health FAIL and the
OverCommitted Fail still outrank it; a Pending pod with no running job keeps
the generic Fail and its sizing advice.

Mutation: moving the combined arm back below the stuck-Pending arm reddens
the new both-signals test with Bugbot's exact output.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
LukasWodka added a commit that referenced this pull request Sep 6, 2026
…ady computes, and tells a permanent shortage from a transient one (backend#2870) (#639)

* fix(doctor): the node-capacity check compares the free memory it already computes, and tells a permanent shortage from a transient one (backend#2870)

Node capacity: batch-Job pods (the `job-name` label the CLI already uses to
find a training pod) go into their own per-node sum instead of being dropped.
The steady-state fit still decides the PERMANENT Fail (OverCommitted, now with
the node's free figures); a node that fits beside the platform but not beside
its running Jobs is the new TRANSIENT Warn (HeldByRunningJob) with the
opposite remedy — wait or stop the job, do not resize. The rollup gets a
matching arm so the top line says the next run waits instead of a green.

Machine capacity: requestedMemory returns its read error instead of 0, so an
unreadable pod list renders "unrequested: unknown" and StatusUnknown rather
than the whole VM as free under a check-mark. The over-commit Warn, which
needs no pod list, still fires.

VERSION 0.10.24 (v0.10.23 is tagged).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(doctor): a Pending pod beside a running job names the job, not "not enough compute" (backend#2870)

Bugbot High on #639: the transient shortage's own symptom is a second
training pod Pending until the running job frees the room, and the
stuck-Pending Fail arm matched it first -- "Not ready ... not enough free
compute" with `resources set max` at exit 2, in exactly the case the
HeldByRunningJob Warn was written for.

A combined arm now sits above the stuck-Pending arm: when Pod health reports
a pod Pending past grace AND Node capacity has measured that a running job
holds the room, the top line says the next training is waiting for the
running one, the remedy says wait or stop the job and that resizing will
not help, and -- since checkPods cannot see WHY a pod is Pending -- what to
do if the wait outlives the job. Warn, not Fail: the inference "Pending so
cannot schedule" is refuted by the measured cause, and a Fail would exit 2
on healthy training (the Bugbot High on #628). A Pod-health FAIL and the
OverCommitted Fail still outrank it; a Pending pod with no running job keeps
the generic Fail and its sizing advice.

Mutation: moving the combined arm back below the stuck-Pending arm reddens
the new both-signals test with Bugbot's exact output.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.

2 participants