Skip to content

fix(kwok): fail closed on unmapped profiles; skip in batch - #2033

Merged
mchmarny merged 2 commits into
NVIDIA:mainfrom
framsouza:fix/kwok-fail-closed-unmapped-profile
Aug 4, 2026
Merged

fix(kwok): fail closed on unmapped profiles; skip in batch#2033
mchmarny merged 2 commits into
NVIDIA:mainfrom
framsouza:fix/kwok-fail-closed-unmapped-profile

Conversation

@framsouza

Copy link
Copy Markdown
Contributor

Replace apply-nodes.sh's hardcoded profile mapping with label-driven
discovery so unmapped (service, accelerator) combinations can no longer
silently fall back to eks/p5-h100.yaml.

Motivation / Context

get_profiles() in kwok/scripts/apply-nodes.sh hardcoded a small
service+accelerator → profile map and defaulted everything else to
amd64 H100. #1985 hit the failure mode: four GB300 GKE lanes reported
green while the CI logs showed the simulated nodes were amd64 H100 —
the arm64 GB300 profiles the PR added were never exercised. The tests
said "your recipe schedules correctly" when they had actually tested
completely different hardware.

Fixes: #1997
Related: #1985

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • Build/CI/tooling

Component(s) Affected

  • Other: KWOK simulation scaffolding (kwok/scripts/, kwok/README.md, .github/workflows/kwok-recipes.yaml)

Implementation Notes

Profile selection moves to kwok/scripts/lib/profile-select.sh and is
driven by profile-declared labels rather than a hardcoded case
statement. select_profiles(service, accelerator, profiles_root) walks
kwok/profiles/<service>/ and matches:

Role Match rule
system provider == <service> and nodeType == system
gpu provider == <service> and nodeType == accelerated and accelerator == <accelerator>

Exactly one match per role is required. Zero or multiple matches — or
an unknown service/accelerator — is an error, with a diagnostic that
enumerates what is on disk. Adding a profile now requires only a YAML
file with the right labels; no bash edit.

Direct-vs-batch semantics are intentionally different:

  • Direct (apply-nodes.sh <recipe>, make kwok-e2e RECIPE=…) —
    fails closed with the full diagnostic. If you explicitly ask about a
    recipe, you get the honest answer.
  • Batch (run-all-recipes.sh, make kwok-test-all, CI) — skips
    unmapped recipes with a WARN and returns rc=0. Matrix coverage
    stays green while profiles are backfilled in follow-up PRs; a recipe
    that can't be simulated is not the same as a recipe that failed.

resolve_recipe_criteria is extracted alongside select_profiles so
apply-nodes.sh and run-all-recipes.sh share one defaulting policy
(null/anyeks/h100) and cannot drift.

Exposed by this change (currently silently passing, will SKIP after
merge until profiles are added): a100-eks-*, all gke-*, all aks-*,
all oke-*, all bcm-* recipes. Each is a candidate for a follow-up
profile PR.

Testing

bash kwok/scripts/lib/profile-select_test.sh   # 14/14 pass
bash kwok/scripts/lib/sync-budget_test.sh      #  9/9 pass (unaffected)
shellcheck -x kwok/scripts/lib/profile-select*.sh kwok/scripts/apply-nodes.sh kwok/scripts/run-all-recipes.sh
yamllint .github/workflows/kwok-recipes.yaml
bash -n kwok/scripts/{apply-nodes.sh,run-all-recipes.sh,lib/profile-select.sh,lib/profile-select_test.sh}

New unit tests cover: happy paths (eks/h100, eks/gb200), unmapped
service, unmapped accelerator, missing system profile, ambiguous
system/gpu profiles, mislabeled provider in wrong directory, argument
validation, nonexistent roots, and resolve_recipe_criteria
explicit/default/any/missing-overlay paths. End-to-end skip logic
verified against the real overlay tree — 4 mapped recipes proceed,
7 unmapped ones skip cleanly.

Risk Assessment

  • Low — Isolated to KWOK scripts; no Go code touched, no
    production code paths affected, purely CI/dev-loop scaffolding.

Rollout notes: No user-visible change. Recipes without matching
profiles will move from silent false-passes to explicit SKIP lines in
the CI logs; follow-up PRs add the missing profiles per provider/GPU.

Checklist

  • Tests pass locally (bash kwok/scripts/lib/profile-select_test.sh; no Go changes so make test is unchanged)
  • Linter passes (shellcheck, yamllint, bash -n)
  • I did not skip/disable tests to make CI green
  • I added/updated tests for new functionality
  • I updated docs if user-facing behavior changed (kwok/README.md)
  • Changes follow existing patterns in the codebase (mirrors lib/sync-budget.sh + sync-budget_test.sh)
  • Commits are cryptographically signed (git commit -S)

@framsouza
framsouza requested review from a team as code owners August 4, 2026 15:38
@copy-pr-bot

copy-pr-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Welcome to AICR, @framsouza! Thanks for your first pull request.

Before review, please ensure:

  • All commits are signed off per the DCO
  • CI checks pass (tests, lint, security scan)
  • The PR description explains the why behind your changes

A maintainer will review this soon.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

KWOK now discovers system and GPU profiles from metadata labels instead of fixed mappings. Recipe criteria default to eks and h100 when absent or set to any. Direct node creation and explicit recipe execution fail on invalid or ambiguous selection. Batch execution skips recipes without matching profiles. The workflow filters unmapped recipes, validates manual dispatches, and runs all script test suites.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested labels: area/tests, area/infra

Suggested reviewers: mchmarny

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the fail-closed profile behavior and batch skipping implemented by the pull request.
Description check ✅ Passed The description explains the profile discovery change, direct-versus-batch behavior, motivation, testing, and affected KWOK scaffolding.
Linked Issues check ✅ Passed The changes address issue #1997 by removing silent fallback mappings, validating profile metadata, and failing closed for unmapped direct recipe execution.
Out of Scope Changes check ✅ Passed The workflow, documentation, scripts, shared library, and tests all support the linked profile-selection and fail-closed objectives.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/kwok-recipes.yaml:
- Around line 95-97: Update the workflow test step running sync-budget_test.sh
and profile-select_test.sh to verify yq is available with command -v yq and exit
non-zero when it is missing, ensuring profile-selection assertions cannot be
silently skipped in CI.

In `@kwok/README.md`:
- Around line 152-164: Update the new cloud provider onboarding guidance to
require metadata.labels.accelerator: <accelerator> alongside provider and
nodeType for accelerated profiles, matching the selection behavior in
select_profiles. Keep the existing directory and system/accelerated profile
requirements unchanged.
- Around line 57-70: The profile-selection documentation must describe criteria
normalization before matching profiles. Update the README to state that
resolve_recipe_criteria maps missing or null service and accelerator values, as
well as any, to eks and h100 respectively, and that apply-nodes.sh and
run-all-recipes.sh use this shared resolver.

In `@kwok/scripts/lib/profile-select.sh`:
- Around line 77-94: Separate unmapped-profile skips from selector failures: in
kwok/scripts/lib/profile-select.sh lines 77-94 and 122-142, return a dedicated
no-match status only for policy-approved absent services/profiles, while keeping
ambiguous matches and invalid or malformed selector states nonzero with
diagnostics. In kwok/scripts/run-all-recipes.sh lines 261-266, succeed only for
that dedicated status and log and propagate every other failure. In
kwok/scripts/lib/profile-select_test.sh lines 91-145, add or update tests
asserting distinct absent and ambiguous outcomes.
- Around line 42-45: Validate $# before expanding positional parameters in
resolve_recipe_criteria and select_profiles, returning their intended
diagnostics when called without arguments instead of triggering set -u errors.
Update kwok/scripts/lib/profile-select.sh lines 42-45 and 63-70 accordingly, and
add or adjust tests in kwok/scripts/lib/profile-select_test.sh lines 137-140 to
invoke both functions without arguments and assert the expected errors.
- Around line 49-52: Update the criteria parsing in profile selection to
distinguish missing/null values from boolean or other invalid types, while still
accepting the string "any" and defaulting only missing/null criteria to "eks" or
"h100". Ensure both yq queries propagate evaluation or malformed-YAML failures
instead of silently selecting defaults, and add coverage for boolean criteria
and malformed YAML.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 59221ab9-3ce8-4825-a63f-b7bd09247244

📥 Commits

Reviewing files that changed from the base of the PR and between edb12ce and 6b6a64f.

📒 Files selected for processing (6)
  • .github/workflows/kwok-recipes.yaml
  • kwok/README.md
  • kwok/scripts/apply-nodes.sh
  • kwok/scripts/lib/profile-select.sh
  • kwok/scripts/lib/profile-select_test.sh
  • kwok/scripts/run-all-recipes.sh

Comment thread .github/workflows/kwok-recipes.yaml
Comment thread kwok/README.md Outdated
Comment thread kwok/README.md Outdated
Comment thread kwok/scripts/lib/profile-select.sh Outdated
Comment thread kwok/scripts/lib/profile-select.sh Outdated
Comment thread kwok/scripts/lib/profile-select.sh
@github-actions github-actions Bot added size/XL and removed size/L labels Aug 4, 2026

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@kwok/README.md`:
- Around line 180-184: Update the new cloud provider profile requirements in the
README to require exactly one system profile per provider and exactly one
accelerated profile for each supported accelerator, replacing the current “at
least one” wording while preserving the specified metadata labels.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: c886fc6e-e4af-475d-8b6b-9e84efa489df

📥 Commits

Reviewing files that changed from the base of the PR and between d7f75af and a1d785b.

📒 Files selected for processing (1)
  • kwok/README.md

Comment thread kwok/README.md Outdated

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@kwok/scripts/lib/profile-select_test.sh`:
- Around line 183-193: Add a test fixture and assertions in the
profile-selection test suite for an overlay with service set to null and
accelerator set to an empty string, verifying resolve_recipe_criteria returns
eks h100 with success. Update the reported test count to include this case,
alongside the existing missing-field and any-placeholder tests.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 880328a4-ccdb-44d6-9f72-e412ce0f9924

📥 Commits

Reviewing files that changed from the base of the PR and between a1d785b and ea60d2e.

📒 Files selected for processing (2)
  • kwok/scripts/lib/profile-select.sh
  • kwok/scripts/lib/profile-select_test.sh

Comment thread kwok/scripts/lib/profile-select_test.sh
@framsouza

Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Reviews resumed.

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
kwok/scripts/lib/profile-select.sh (1)

162-184: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Propagate profile metadata read failures.

select_profiles ignores failures from the yq eval calls that read provider, nodeType, and accelerator. A malformed profile YAML can therefore produce empty labels and return PROFILE_SELECT_RC_NO_MATCH.

run_recipe_test treats that status as a successful skip. This can make batch CI pass when the profile tree is invalid.

Check each yq command and return status 1 with a diagnostic on failure. Add a malformed-profile fixture that asserts status 1.

As per coding guidelines, write tests alongside code changes.

Proposed fix
-        provider=$(yq eval '.metadata.labels.provider // ""' "${profile}")
-        nodeType=$(yq eval '.metadata.labels.nodeType // ""' "${profile}")
+        if ! provider=$(yq eval '.metadata.labels.provider // ""' "${profile}"); then
+            echo "[ERROR] failed to read provider label from ${profile}" >&2
+            return 1
+        fi
+        if ! nodeType=$(yq eval '.metadata.labels.nodeType // ""' "${profile}"); then
+            echo "[ERROR] failed to read nodeType label from ${profile}" >&2
+            return 1
+        fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kwok/scripts/lib/profile-select.sh` around lines 162 - 184, Update
select_profiles so every yq eval reading provider, nodeType, or accelerator
checks its exit status, emits a diagnostic identifying the malformed profile,
and returns status 1 immediately on failure instead of treating missing labels
as no match. Add a malformed-profile fixture and accompanying run_recipe_test
coverage asserting status 1, placing the test alongside the implementation
changes.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@kwok/scripts/lib/profile-select.sh`:
- Around line 162-184: Update select_profiles so every yq eval reading provider,
nodeType, or accelerator checks its exit status, emits a diagnostic identifying
the malformed profile, and returns status 1 immediately on failure instead of
treating missing labels as no match. Add a malformed-profile fixture and
accompanying run_recipe_test coverage asserting status 1, placing the test
alongside the implementation changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: fa004579-5680-43b3-97ad-d3c17b1a4feb

📥 Commits

Reviewing files that changed from the base of the PR and between a1d785b and 05a4970.

📒 Files selected for processing (4)
  • kwok/README.md
  • kwok/scripts/lib/profile-select.sh
  • kwok/scripts/lib/profile-select_test.sh
  • kwok/scripts/run-all-recipes.sh

@mchmarny mchmarny left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Requesting changes: 1 remaining merge blocker on 2af2b69.

Welcome to AICR, @framsouza, and thanks for taking on this false-positive gap. The label-driven selection and focused regression suite are a strong direction. I verified the latest malformed-profile fix locally (22/22 tests pass), so CodeRabbit's earlier blocker is addressed. The remaining inline issue is the CI no-match path: explicit matrix jobs can still report success without running bundle or scheduling validation. Once that path cannot produce a green false-success, happy to re-review.

Current required CI has not run yet because external-contributor validation is still pending.

sel_err=$(select_profiles "${svc}" "${accel}" "${KWOK_DIR}/profiles" 2>&1 >/dev/null) || select_rc=$?
if (( select_rc == PROFILE_SELECT_RC_NO_MATCH )); then
log_warn "SKIP ${recipe}: no KWOK profile for service=${svc} accelerator=${accel} (add one under kwok/profiles/${svc}/ — see #1997)"
return 0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Blocking: could we keep an unmapped profile from becoming a successful CI job? Each matrix cell calls run-all-recipes.sh with one explicit recipe, but this returns 0 and main records it in passed. On this head, 72 of 90 service-scoped overlays resolve to PROFILE_SELECT_RC_NO_MATCH, so those jobs can remain green without bundle or scheduling validation - the false-success direction #1997 is meant to close. Please make explicit or CI recipes propagate a nonzero result or gate no-coverage separately, and add a regression showing that a single unmapped recipe cannot be reported as passed.

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.

Thanks for the specific reproduction, the 72/90 number pushed me to close both layers on branch head f8242bf:

  • Non-zero on explicit invocation: kwok/scripts/run-all-recipes.sh gb200-oke-training (exact form used at .github/actions/kwok-test/action.yml:178) exits 1 with a clear diagnostic. main records it in failed[], not passed[]. Implicit batch mode still SKIPs so make kwok-test-all locally isn't disrupted.
  • No-coverage gated at CI: c069573 filters unmapped recipes at the classify step, local sim reproduces your 18-in / 72-dropped split.
  • Regression: kwok/scripts/run-all-recipes_test.sh asserts explicit-unmapped-recipe-is-not-reported-as-passed (rc must be non-zero). Passes on this head.

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
kwok/scripts/lib/profile-select.sh (1)

178-205: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Propagate find failures as fatal selector failures.

find runs in process substitution on Line 205. Bash does not propagate that command exit status to the while loop. If traversal fails, both match arrays can remain empty. Lines 207-210 then return PROFILE_SELECT_RC_NO_MATCH instead of 1.

Batch execution can skip a broken profile tree and return success, even though profile discovery failed. Capture and check the find exit status before returning no-match. Add a regression test that makes find fail and asserts status 1.

As per coding guidelines, write tests alongside code changes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@kwok/scripts/lib/profile-select.sh` around lines 178 - 205, Update the
profile-selection loop around the process-substitution find invocation to
capture its exit status and return 1 when traversal fails, before the existing
no-match handling returns PROFILE_SELECT_RC_NO_MATCH. Add a regression test that
forces find to fail and verifies the selector exits with status 1.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@kwok/scripts/lib/profile-select.sh`:
- Around line 178-205: Update the profile-selection loop around the
process-substitution find invocation to capture its exit status and return 1
when traversal fails, before the existing no-match handling returns
PROFILE_SELECT_RC_NO_MATCH. Add a regression test that forces find to fail and
verifies the selector exits with status 1.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 65aa935e-de16-492f-9631-063b1f026ac4

📥 Commits

Reviewing files that changed from the base of the PR and between 05a4970 and 2af2b69.

📒 Files selected for processing (2)
  • kwok/scripts/lib/profile-select.sh
  • kwok/scripts/lib/profile-select_test.sh

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/kwok-recipes.yaml:
- Around line 192-200: Update both profile_status handling sites at
.github/workflows/kwok-recipes.yaml lines 192-200 and 244-249 to capture the
command substitution output and exit status separately. Preserve fatal nonzero
statuses by terminating discovery, add overlays to dropped only when the status
equals PROFILE_SELECT_RC_NO_MATCH, and retain the existing skip behavior for
that specific no-match result.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 3b195816-63ea-4af2-9af2-a3110ef347be

📥 Commits

Reviewing files that changed from the base of the PR and between 2af2b69 and c069573.

📒 Files selected for processing (1)
  • .github/workflows/kwok-recipes.yaml

Comment thread .github/workflows/kwok-recipes.yaml

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/kwok-recipes.yaml (1)

112-146: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Move recipe classification out of the workflow YAML.

This step now handles criteria resolution, profile selection, dispatch validation, tier discovery, diff traversal, and batching. Move this logic to a composite action or a checked-in script. Keep the workflow step focused on orchestration. If the logic remains inline, document why composite indirection is not appropriate.

Based on learnings, prefer the repository’s three-layer composite architecture; inline shell is an exception only for narrowly scoped, single-use steps with an inline rationale.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/kwok-recipes.yaml around lines 112 - 146, The workflow
step is accumulating recipe classification and discovery logic that should not
remain inline. Move profile and recipe classification, including functions such
as profile_status and related criteria/profile selection, into the repository’s
established three-layer composite-action architecture (or a checked-in script
invoked by it), leaving the workflow responsible only for orchestration, inputs,
outputs, and batching; if inline logic is retained, add an explicit rationale
explaining why composite indirection is inappropriate.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In @.github/workflows/kwok-recipes.yaml:
- Around line 112-146: The workflow step is accumulating recipe classification
and discovery logic that should not remain inline. Move profile and recipe
classification, including functions such as profile_status and related
criteria/profile selection, into the repository’s established three-layer
composite-action architecture (or a checked-in script invoked by it), leaving
the workflow responsible only for orchestration, inputs, outputs, and batching;
if inline logic is retained, add an explicit rationale explaining why composite
indirection is inappropriate.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 6a6565f3-bed7-44bc-850a-3680a557d4f4

📥 Commits

Reviewing files that changed from the base of the PR and between c069573 and f8242bf.

📒 Files selected for processing (3)
  • .github/workflows/kwok-recipes.yaml
  • kwok/scripts/run-all-recipes.sh
  • kwok/scripts/run-all-recipes_test.sh

@framsouza

Copy link
Copy Markdown
Contributor Author

@mchmarny thanks for catching that. I verified end-to-end locally (branch head f8242bf). kwok/scripts/run-all-recipes.sh gb200-oke-training, the exact invocation .github/actions/kwok-test/action.yml:178 uses per matrix cell exits 1 with [ERROR] Recipe gb200-oke-training was explicitly requested but has no KWOK profile.... Both the discovery filter (c069573) and the script-layer defense (f8242bf) are in place; regression at kwok/scripts/run-all-recipes_test.sh proves both sides of the asymmetry. Pls let me know if i'm missing something

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/kwok-recipes.yaml:
- Around line 198-205: Update both yq overlay-parsing sites in
.github/workflows/kwok-recipes.yaml at lines 198-205 and 254-261: replace the yq
eval || true handlers with explicit status checks that immediately exit
discovery when parsing fails, rather than treating the empty result as an
unmapped recipe; preserve the existing filtering behavior for successfully
parsed overlays.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 8626754e-79ee-43b2-86e9-0fe72a00368e

📥 Commits

Reviewing files that changed from the base of the PR and between f8242bf and 00c37cc.

📒 Files selected for processing (1)
  • .github/workflows/kwok-recipes.yaml

Comment thread .github/workflows/kwok-recipes.yaml

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/kwok-recipes.yaml (1)

214-218: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Apply shared criteria defaults before filtering recipes.

resolve_recipe_criteria maps absent, null, and any service criteria to eks. These checks skip those overlays before profile_status calls that resolver. A recipe that direct execution selects as eks/h100 is therefore omitted from the CI matrix.

  • .github/workflows/kwok-recipes.yaml#L214-L218: Skip only explicitly unsupported ocp recipes. Let absent, null, and any values reach profile_status.
  • .github/workflows/kwok-recipes.yaml#L266-L274: Apply the same rule in Tier 2.

As per PR objectives, recipe criteria must use consistent eks and h100 defaults across execution paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/kwok-recipes.yaml around lines 214 - 218, Update the
filtering logic in .github/workflows/kwok-recipes.yaml at lines 214-218 and
266-274 to skip only explicitly unsupported ocp services; allow absent, null,
and any values to reach profile_status and resolve_recipe_criteria so shared eks
and h100 defaults are applied consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/kwok-recipes.yaml:
- Around line 120-141: Update the comments near the inline helpers
read_overlay_field and profile_status to document why this reusable control flow
remains local to the discovery step instead of being extracted into a Layer-2
composite action: it has no meaningful reuse beyond this workflow step. Keep the
rationale brief and preserve the existing helper behavior.

---

Outside diff comments:
In @.github/workflows/kwok-recipes.yaml:
- Around line 214-218: Update the filtering logic in
.github/workflows/kwok-recipes.yaml at lines 214-218 and 266-274 to skip only
explicitly unsupported ocp services; allow absent, null, and any values to reach
profile_status and resolve_recipe_criteria so shared eks and h100 defaults are
applied consistently.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: d2c6e828-94ac-456e-858b-5e252ec459b3

📥 Commits

Reviewing files that changed from the base of the PR and between 00c37cc and f9946dd.

📒 Files selected for processing (1)
  • .github/workflows/kwok-recipes.yaml

Comment thread .github/workflows/kwok-recipes.yaml
@mchmarny

mchmarny commented Aug 4, 2026

Copy link
Copy Markdown
Member

@framsouza also please squash commits

apply-nodes.sh::get_profiles silently fell back to eks/p5-h100.yaml
for any unknown service+accelerator. NVIDIA#1985 showed the failure mode:
GB300 GKE lanes reported green while running amd64 H100 nodes.

Replace the hardcoded map with label-driven discovery in
kwok/scripts/lib/profile-select.sh (match metadata.labels
{provider,nodeType,accelerator}). Adding a profile is now a YAML
file, no bash edit. Layered fail-closed:

  - Direct (apply-nodes.sh, make kwok-e2e): full diagnostic, exit 1.
  - Batch (run-all-recipes.sh): explicit invocations — every CI
    matrix cell — fail on unmapped SKIP; implicit get_recipes()
    still SKIPs for local make kwok-test-all.
  - CI discovery (kwok-recipes.yaml classify): unmapped recipes
    never enter the matrix; malformed overlay YAML aborts classify;
    workflow_dispatch on an unmapped recipe fails at classify.

Selector tightened: reject booleans/integers in criteria (yq's //
alternative was falsy-swallowing false); malformed profile YAML is
fatal; ambiguous match returns rc=1, genuine no-match returns
PROFILE_SELECT_RC_NO_MATCH (rc=2) so batch can distinguish
"no profile yet" from "broken tree".

Tests wired into the discover job's script-tests step
(profile-select_test.sh: 22 cases; run-all-recipes_test.sh: 2 cases
proving explicit unmapped cannot be passed). kwok/README.md updated.

Fixes NVIDIA#1997

Signed-off-by: framsouza <fram.souza14@gmail.com>
@framsouza
framsouza force-pushed the fix/kwok-fail-closed-unmapped-profile branch from 630cff3 to 5e11668 Compare August 4, 2026 17:22

@mchmarny mchmarny left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

APPROVE — prior blocker resolved on 1275249. Explicit unmapped recipes now fail instead of being reported as passed, and the regression test covers the CI invocation shape. Focused shell tests pass; live CI has no failures and remaining lanes are still running.

@mchmarny
mchmarny enabled auto-merge (squash) August 4, 2026 18:22
@mchmarny
mchmarny merged commit 73cd9ec into NVIDIA:main Aug 4, 2026
51 checks passed

@njhensley njhensley left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🔬 Multi-persona review — approve with comments

Method: four independent persona reviewers (bash-correctness · CI/DX-operability · test-coverage · domain/fail-closed) whose findings were each re-derived from the resolved code by an adversarial senior meta-reviewer. Unit tests run locally (profile-select_test 22/22, run-all-recipes_test 2/2); shellcheck -x clean on the changed scripts.

Tier legend: 🔴 Blocker · 🟠 Major · 🟡 Minor · 🔵 Nitpick

Overall

Strong PR. It replaces the silent-fallback profile map (root cause of the #1985 false-green — arm64 GB300 GKE lanes validated on amd64 H100 nodes) with label-driven, fail-closed selection, a well-reasoned direct-vs-batch asymmetry, and a thorough unit suite. The bash is careful: subshell-exit propagation is handled at every $(...) call site, the skippable-NO_MATCH vs fatal-rc=1 split is disciplined, and the yq-required gate fails closed without adding an unpinned install.

@mchmarny's CHANGES_REQUESTED blocker is resolved on this head, three independent ways:

  1. The classify-step drop filter removes unmapped recipes before dispatch — no matrix cell is ever created.
  2. Defense-in-depth: the cell invokes run-all-recipes.sh --deployer X <recipe> positionally (action.yml:178) → EXPLICIT_RECIPESrun_recipe_test returns rc=1, not a skip.
  3. A regression test proves an explicitly-named unmapped recipe cannot be reported as passed.

No blockers survived meta-review. Approving; the findings below are Minor/Nitpick. The one worth landing is F1 — a new adjacent fail-open the drop-filter introduces.

Findings not anchorable to the diff (pre-existing lines)

🟡 F1 — Empty-tier fail-open: no minimum-coverage floor. The new drop-filter can empty a whole tier. Tier jobs gate on tierN_pairs != '[]', so an empty tier is skipped, and the (pre-existing) summary job at .github/workflows/kwok-recipes.yaml L497-512 treats a skipped tier as pass (it only fails on failure/cancelled). Because only kwok/profiles/eks/ ships today, deleting/renaming that directory makes every recipe resolve to the skippable PROFILE_SELECT_RC_NO_MATCH, empties all three tiers, and the run prints "All recipe validations passed" with zero bundle/scheduling coverage. Downgraded to Minor because reaching zero coverage requires removing the whole profile tree (a conspicuous diff); the realistic single-profile case is safe exclusion. Fix: floor guard in discover — fail when the push/nightly tier3 (or tier1) pair count is 0 (::error::KWOK matrix is empty — profile tree likely broken). Only workflow_dispatch no-match should legitimately produce no cells.

🟡 F1b — summary needs the tier jobs but not discover (kwok-recipes.yaml L473). This PR widens discover's fatal-exit surface; if discover fails, the tiers skip and summary (if: always()) sees them skipped → exit 0. The overall workflow run still goes red, so this only bites if KWOK Test Summary specifically is the required branch-protection check. Fix: add discover to summary's needs and fail when needs.discover.result != 'success'.

🔵 F11 — pre-existing / out of scope: kwok/scripts/apply-nodes.sh:152 hardcodes DEFAULT_REGION/DEFAULT_ZONES to us-east-1 for every provider. Untouched by this PR and inert today (only eks profiles), but latent once gke/aks profiles land. Follow-up, not a merge concern.

Confirmed non-issues (examined & cleared)

  • @mchmarny's blocker — resolved at both discovery (drop) and in-runner (explicit-fail).
  • yq-required gate — fails closed, no unpinned install (matches the repo convention that ubuntu-latest preinstalls yq/jq).
  • Silent eks/p5-h100 fallback — fully removed from all changed scripts.
  • workflow_dispatch fails loud on an unmapped recipe; local make kwok-test-all still skips → no dev-loop regression.

Summary

🔴 0 · 🟠 0 · 🟡 3 · 🔵 8 — Approve with comments. The single follow-up worth landing is F1's coverage-floor guard; everything else is polish.

Reviewed with a multi-persona + adversarial-meta-review pass. Inline comments follow.

# caller's variable via $(...) and become an annotation only in
# the step log. Caller MUST use `|| exit 1` — an inner exit
# inside command substitution only kills the subshell.
read_overlay_field() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🟡 Minor — CI read_overlay_field reuses the // "" falsy-swallow the library reader abandoned

The library's _read_criteria_field was rewritten to reject service: false by YAML tag, but classify still reads service via yq '... // ""' here and does -zcontinue (~L217) before the strict resolve_recipe_criteria runs. So service: false collapses to "" and is silently dropped as non-testable instead of erroring like the direct path does. Contrived input, but it's an inconsistency with the fix's own stated guarantee.

Blast radius: Coverage-exclusion of a malformed overlay (not a wrong-hardware false-pass); the direct run_recipe_test path still fails the same input strictly.

Fix: Route the service field through the strict reader in classify (or add a tag check) so a malformed-typed service errors instead of being silently excluded.

profile_status() {
local overlay="$1" criteria svc accel rc=0
if ! criteria=$(resolve_recipe_criteria "${overlay}" 2>/dev/null); then
echo "::error file=${overlay}::resolve_recipe_criteria failed"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🔵 Nitpick — profile_status writes ::error:: to stdout inside $(...), so the annotation is captured and discarded

Both ::error:: lines here (L153 and L163) lack >&2, unlike the sibling read_overlay_field (L136) whose header documents the rule. profile_status is always called as status=$(profile_status ...), so these lines are captured into the discarded var and never render as GitHub annotations. Both branches exit 1 (caught by || exit 1 at every call site), so the job still fails closed and the raw stderr re-emit (L154/L164) still shows — only the file-pinned annotation is lost.

Blast radius: Lost operator-facing annotation; no misclassification, no false green.

Fix: Add >&2 to the two echo "::error..." lines to match read_overlay_field.

log_warn "SKIP ${recipe}: no KWOK profile for service=${svc} accelerator=${accel} (add one under kwok/profiles/${svc}/ — see #1997)"
return 0
fi
if (( select_rc != 0 )); then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🔵 Nitpick — FATAL rc=1 propagation in run_recipe_test is untested at the integration layer

Both harness cases drive gb200-oke-training (rc=2 no-match), exercising the explicit-fail and SKIP branches but never this select_rc != 0 → return select_rc path — the dup/broken-profile-must-not-be-swallowed case central to mchmarny's concern. select_profiles returning 1 is unit-covered (profile-select_test tests 6/7), and this propagation line is trivially correct, so it's a coverage gap rather than a defect.

Blast radius: A future edit that reordered these branches to swallow rc=1 as a skip would have no regression guard.

Fix: Add a run-all-recipes_test.sh case with a temp KWOK_DIR fixture containing duplicate system/GPU profiles + matching overlay; assert run_recipe_test returns 1 in both explicit and implicit modes.

Comment thread kwok/README.md
**Direct vs batch semantics.** `apply-nodes.sh <recipe>` (and
`make kwok-e2e RECIPE=...`) fails closed with the full diagnostic when
no profile matches. `run-all-recipes.sh` (and `make kwok-test-all`,
plus the CI matrix) instead **skips** unmapped recipes with a WARN so

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🟡 Minor — README direct-vs-batch says the CI matrix 'skips unmapped recipes with a WARN' — CI actually DROPS at classify and explicit-fails

Accurate only for implicit make kwok-test-all. In CI, discover DROPS the recipe at classify time (::notice, never dispatched); and an explicitly-named recipe reaching run-all-recipes.sh via action.yml:178 fails rc=1, not skips. This paragraph is the canonical description of the new fail-closed contract, so the drift is worth fixing.

Blast radius: Doc-only, but a future contributor could 'fix' the explicit-fail path believing CI is meant to skip, reopening the false-green hole.

Fix: Document three distinct modes — direct → fail closed; implicit make kwok-test-all → SKIP-with-WARN; CI → DROP at classify + explicit-fail — and qualify 'Zero matches is a hard error' as direct-path only.

relpath="${profile#"${profiles_root}"/}"
# Skip anything whose provider label disagrees with its directory —
# protects against a mis-copied file living under the wrong service.
if [[ "${provider}" != "${service}" ]]; then

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🔵 Nitpick — provider != directory mismatch is silently continue'd, not a hard error

A profile whose provider label disagrees with its parent directory is skipped. For a stray file this is the intended defense (documented at L188-189). But if the mislabeled file is the SOLE system/GPU profile for that role, the mislabel degrades to an invisible rc=2 no-match (batch SKIP / CI drop) rather than surfacing the tree-integrity fault.

Blast radius: A single-character typo in provider on the sole eks profile silently zeroes coverage for every eks recipe, routed through the skippable no-match path.

Fix: Optional: return rc=1 with a diagnostic when a file under / declares provider=, rather than continue.

EXPLICIT_RECIPES="${UNMAPPED_RECIPE}"
rc=0
run_recipe_test "${UNMAPPED_RECIPE}" >/dev/null 2>&1 || rc=$?
check "explicit-unmapped-recipe-is-not-reported-as-passed" ne 0 "${rc}"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🔵 Nitpick — Regression test 1 asserts only rc != 0 with output discarded — vacuous-pass risk

apply-nodes.sh is not stubbed (only cleanup_between_tests is). If the explicit-fail branch were removed, execution would fall through to apply-nodes.sh and fail there (no cluster) → rc!=0, and the test would still PASS — for a downstream failure, not the explicit-unmapped guard it claims to protect. ne 0 can't distinguish 'failed for the right reason' from 'failed for any reason'.

Blast radius: The test proving explicit-unmapped fails closed can go green even after that logic is removed.

Fix: Assert rc == 1 specifically and capture the log to assert it contains the explicit-branch diagnostic ('explicitly requested but has no KWOK profile').

# resolution (which uses BASH_SOURCE) points at the real lib directory.
TMP_SOURCE=$(mktemp "${SCRIPT_DIR}/.run-all-recipes.test.XXXXXX.sh")
trap 'rm -f "${TMP_SOURCE}"' EXIT
grep -v '^main "\$@"$' "${SCRIPT_UNDER_TEST}" > "${TMP_SOURCE}"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🔵 Nitpick — Harness strips main "$@" with an exact-anchored grep; a reformat silently no-ops the strip

grep -v '^main "\$@"$' removes the tail call by exact-line match before sourcing. If run-all-recipes.sh's tail line ever changes (trailing whitespace, exec main, a comment), the pattern stops matching, main "$@" stays in the temp copy, and sourcing executes main() with the test's args — driving into real kind/kubectl ops. No assertion that a line was removed.

Blast radius: Currently benign; would fail loudly (cluster ops) rather than pass silently, so future-fragility only.

Fix: Assert exactly one line was removed (or that main "$@" is absent from TMP_SOURCE) before sourcing; optionally define a no-op main() after sourcing.

echo "${fails} test(s) failed"
exit 1
fi
echo "All 22 tests passed"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🔵 Nitpick — 'All 22 tests passed' is a hardcoded literal, not a derived counter

The success banner prints a literal count rather than a per-check counter. Counts match today, but if a contributor adds a case and forgets to bump the string — or an early exit skips trailing checks without failing — the harness can print 'All 22 tests passed' while a different number ran, and CI stays green.

Blast radius: A silently short-circuited or under-counted run reports success.

Fix: Increment a ran counter inside check() and print/assert it (echo "All ${ran} tests passed").

write_overlay "${OVERLAY_DIR}/missing.yaml"
got_out=$(resolve_recipe_criteria "${OVERLAY_DIR}/missing.yaml" 2>/dev/null)
got_rc=$?
check "resolve-missing-defaults-to-eks-h100" 0 "eks h100" "" "${got_rc}" "${got_out}" ""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🔵 Nitpick — Untested select_profiles / criteria branches

No case covers: an unknown nodeType (the case statement has no default arm, so a typo'd nodeType silently drops the profile → skippable no-match), an accelerated profile missing its accelerator label (joins available_accels as ""), a mislabeled SYSTEM profile (test 8 covers only the accelerated role), or mixed explicit-service + defaulted-accelerator criteria.

Blast radius: A one-character typo in nodeType/provider on a real profile turns a should-run recipe into a silent skip — the same coverage-lie the PR targets, via a different field.

Fix: Add the four cases; consider a default nodeType arm that warns on unrecognized values and test it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

kwok: fail closed when no node profile matches the recipe

3 participants