Skip to content

fix(kwok): deadline-derived sync-gate budgets + 18m tier timeout - #1708

Merged
mchmarny merged 4 commits into
NVIDIA:mainfrom
ArangoGutierrez:fix/kwok-sync-gate-deadline
Jul 13, 2026
Merged

fix(kwok): deadline-derived sync-gate budgets + 18m tier timeout#1708
mchmarny merged 4 commits into
NVIDIA:mainfrom
ArangoGutierrez:fix/kwok-sync-gate-deadline

Conversation

@ArangoGutierrez

@ArangoGutierrez ArangoGutierrez commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Makes chainsaw sync-gate diagnostics structurally guaranteed in the KWOK CI lane: gate budgets derive from the remaining job time (new KWOK_SYNC_DEADLINE_EPOCH contract) with a fail-fast floor, and tier job timeouts move 15m → 18m so the full budget actually fits.

Motivation / Context

KWOK tier jobs (timeout-minutes: 15) raced the chainsaw sync gates' fixed budgets (flux 500s, argocd 480s): GitHub killed the job ~1 minute before chainsaw would time out and print its catch: diagnostics, so failures surfaced as CANCELLED with zero error report (job 86181256207 on PR #1686 shows the shape). The budget was over-committed even on the good path: ~300s setup + 500s gate + ~180s post-gate diagnostics/verify/artifact-upload ≈ 980s > 900s. Sibling PR #1707 fixes the Docker Hub throttling that made setup slow enough to hit this consistently; this PR guarantees that whenever anything slow happens again, the lane reports instead of silently dying.

Fixes: N/A
Related: #1707, #1686 (diagnosis context)

Type of Change

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

Component(s) Affected

  • Docs/examples (docs/)
  • Other: KWOK CI lane (kwok/scripts/, .github/actions/kwok-test, .github/workflows/kwok-recipes.yaml, .github/workflows/kwok-tier3-shard.yaml)

Implementation Notes

  • kwok/scripts/lib/sync-budget.sh: compute_sync_budget returns min(gate default, deadline − now); below a 120s floor the gate fails fast with the existing GitOps sync-deadline exit code (50) and an explicit "setup consumed the budget" error — itself the diagnosis a silent CANCELLED used to destroy. Env unset (local runs) ⇒ behavior unchanged.
  • Both gates (argocd + flux) consume it; existing KWOK_ARGOCD_SYNC_TIMEOUT/KWOK_FLUX_SYNC_TIMEOUT overrides still work; the derived budget is logged on every run so the margin is tunable from evidence.
  • The kwok-test action derives the deadline in its first step — before toolchain setup and the aicr build — so unaccounted job-start drift stays within the 240s margin's 60s allowance (180s is reserved for post-gate diagnostics/artifacts). The job_timeout_minutes input is required (no default), integer-guarded (positive, no leading zeros), and passed explicitly by all three call sites — a sync-budget_test.sh case asserts each caller's value matches its job's timeout-minutes.
  • 18m tier budget: 1080s fits ~300s setup + 500s gate + 180s margin with ~100s slack. Passing runs are unaffected; only failing runs use the extra time.
  • The sync-budget unit harness runs as a step in the discover job, so a regression fails the lane before any cluster spins up.
  • Chainsaw test files untouched (flux-sync/flux-git-sync byte-identical sibling contract preserved).

Testing

bash kwok/scripts/lib/sync-budget_test.sh                        # 6/6 pass
shellcheck -x -P SCRIPTDIR kwok/scripts/validate-scheduling.sh \
  kwok/scripts/lib/sync-budget.sh kwok/scripts/lib/sync-budget_test.sh  # rc=0
actionlint .github/workflows/kwok-recipes.yaml \
  .github/workflows/kwok-tier3-shard.yaml       # zero new findings vs base
make kwok-e2e RECIPE=aks-inference               # rc=0
make qualify                                     # rc=0

Live behavioral verification on a local kind cluster:

  • Ample deadline, full flux-oci lane: rc=0, gate logs Flux sync timeout: 500s (default 500s; deadline-derived when KWOK_SYNC_DEADLINE_EPOCH is set).
  • Nearly-consumed deadline: gate prints [ERROR] Flux sync gate: <120s left before the job deadline (…) — setup consumed the budget and exits 50 — the failure now reports instead of CANCELLED-with-nothing.

Risk Assessment

  • Medium — Touches multiple components or has broader impact

Rollout notes: CI-only behavior; local runs are unaffected (env unset ⇒ fixed defaults). Revert restores the fixed budgets and 15m timeouts. If the 240s margin proves mistuned, the derived budget is logged on every run for evidence-based adjustment.

Checklist

  • Tests pass locally (make test with -race)
  • Linter passes (make lint)
  • 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
  • Changes follow existing patterns in the codebase
  • Commits are cryptographically signed (git commit -S) — GPG signing info

@github-actions

Copy link
Copy Markdown
Contributor

@coderabbitai

coderabbitai Bot commented Jul 10, 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

Adds a CI-derived absolute sync deadline with a 240-second diagnostic margin. A shared Bash helper computes bounded sync budgets with a 120-second floor, and Argo CD and Flux gates use those budgets. The action validates and exports the deadline, Tier 1–3 workflows pass the job timeout and use 18-minute limits, unit tests run during discovery, and contributor documentation describes the new CI-only variable.

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

Suggested labels: area/tests, theme/validation

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: deadline-derived sync budgets and longer tier timeouts.
Description check ✅ Passed The description is detailed and directly matches the KWOK sync-budget and timeout changes in the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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: 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/actions/kwok-test/action.yml:
- Around line 87-109: Validate in the “Derive sync-gate deadline” step that
JOB_TIMEOUT_MINUTES*60 minus the 240-second margin is at least
SYNC_BUDGET_FLOOR_SECONDS before calculating or exporting
KWOK_SYNC_DEADLINE_EPOCH; emit a clear ::error message and exit nonzero when the
budget is too small, while preserving the existing integer validation and
deadline setup.
🪄 Autofix (Beta)

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: 0577dab7-f3a3-46c9-af7d-6826503e83c7

📥 Commits

Reviewing files that changed from the base of the PR and between 527c015 and 8e7dd70.

📒 Files selected for processing (7)
  • .github/actions/kwok-test/action.yml
  • .github/workflows/kwok-recipes.yaml
  • .github/workflows/kwok-tier3-shard.yaml
  • docs/contributor/tests.md
  • kwok/scripts/lib/sync-budget.sh
  • kwok/scripts/lib/sync-budget_test.sh
  • kwok/scripts/validate-scheduling.sh

Comment thread .github/actions/kwok-test/action.yml
@mchmarny
mchmarny marked this pull request as ready for review July 10, 2026 16:12
@mchmarny
mchmarny requested review from a team as code owners July 10, 2026 16:12
mchmarny
mchmarny previously approved these changes Jul 10, 2026

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

Sound design: budget = min(default, deadline − now) with a 120s fail-fast floor that converts the silent CANCELLED into an explicit "setup consumed the budget" diagnosis, env-unset preserves local behavior, and the unit harness gates the lane from the discover job before any cluster spins up. The flux path reusing EXIT_ARGOCD_SYNC_TIMEOUT matches the pre-existing documented pattern, and anchoring the deadline in the action's first step is the right call. One nit on the job_timeout_minutes default inline; nothing blocking. CI green.

Keep in sync with the calling job's timeout-minutes.
required: false
default: '18'

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.

nit: all three call sites pass job_timeout_minutes explicitly, and the "keep in sync with the calling job's timeout-minutes" contract is manual — a future caller that bumps its timeout-minutes and relies on this stale default silently re-creates the CANCELLED-without-diagnostics failure this mechanism exists to prevent. Dropping the default (making the input required) turns that drift into a loud failure at wiring time.

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.

Made it required and dropped the default in 624a947 — a missing value now fails the derive step's integer validation loudly.

@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

Method: 4 independent persona reviewers (Correctness · CI-DX/Operability · Domain & Architecture · Docs), each finding routed through an adversarial senior meta-reviewer that re-derived it from the resolved code at 624a947d.

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

Overall assessment

This is a well-reasoned, well-tested CI fix. The core mechanism is sound: the deadline is anchored in the action's first step so pre-setup drift stays inside the margin, both gates consume min(default, deadline−now) with a 120s fail-fast floor, the unit harness runs in discover before any cluster spins up, and local runs are unaffected when the env var is unset. The highest-risk theories were independently cleared: the flux gate's reuse of EXIT_ARGOCD_SYNC_TIMEOUT (50) is intended and consumer-correct (run-all-recipes.sh matches argocd-*||flux-* on rc 50), there's no double-charge (one recipe×deployer per job; the absolute deadline shrinks budgets monotonically), the readonly const is sourced exactly once, and the 6 unit tests cover the floor boundary precisely.

Nothing here blocks. What surfaced is latent foot-guns and doc drift, all inline below.

Recommendation: Approve with comments.

✅ Confirmed non-issues (examined and cleared)

  • Flux gate returns EXIT_ARGOCD_SYNC_TIMEOUT (50) — intentional shared code; the header comment scopes 50 to both gates and run-all-recipes.sh matches argocd-*||flux-* on it. Not a naming bug.
  • Double-charging the budget — one recipe×deployer per job; the absolute deadline shrinks budgets monotonically. Correct.
  • readonly SYNC_BUDGET_FLOOR_SECONDS re-source error — sourced exactly once by validate-scheduling.sh; run-all-recipes.sh invokes it as a subprocess. Matches the cleanup.sh contract.
  • set -e arithmetic landmines in compute_sync_budget — every (( )) is an if-condition; the if ! budget=$(…) capture correctly propagates return 1.
  • Cold-cache exit-50 misattribution — already handled: the fail-fast branch logs "…<120s left before the job deadline… — setup consumed the budget", which is the diagnosis.
  • .github/actions/README.md not updated — this PR doesn't touch it, and its kwok-test example already omits other required inputs; illustrative, not copy-paste-complete. Not a regression this PR introduced (though a broader README refresh wouldn't hurt).
  • required: true is advisory for composite inputs — true, but the regex guard rejects the empty/missing case loudly, so the "fails loudly" claim holds.

Summary

🔴 Blocker 0 | 🟠 Major 0 | 🟡 Minor 3 | 🔵 Nitpick 2      Recommendation: Approve with comments

The three 🟡s are all cheap: a sync-assertion for the twin 18s, one doc line, and a two-char regex tightening. None gate merge.

Review method: multi-persona fan-out + adversarial meta-review.

chainsaw_version: ${{ steps.versions.outputs.chainsaw }}
chainsaw_sha256: ${{ steps.versions.outputs.chainsaw_sha256_linux_amd64 }}
kind_node_image: ${{ steps.versions.outputs.kind_node_image }}
job_timeout_minutes: '18'

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 — The two 18s are unguarded hand-synced literals — the dangerous drift direction re-creates the exact bug this PR fixes

The deadline derives from the passed job_timeout_minutes, but GitHub kills the job at the separate timeout-minutes (L294). These are two literals per job, hand-synced across three call sites (tier1 L294↔L326, tier2 L337↔L367, kwok-tier3-shard L38↔L69). If a future edit ever makes job_timeout_minutes > timeout-minutes (e.g. bump the input to 25, forget the job cap), deadline = derive_time + 1500 − 240 lands past the job_start + 1080 kill → min() never shrinks → the gate runs until GitHub kills it → silent CANCELLED-without-diagnostics, the precise failure this PR exists to prevent.

Blast radius: All KWOK tier1/2/3 lanes. Nothing is broken today (all six literals = 18), hence Minor — but a future timeout bump that touches one literal and not the other silently reintroduces the diagnostics-loss regression. GitHub exposes no context for a job's own timeout-minutes, so it genuinely can't be auto-derived.

Fix: Add a grep/unit assertion (alongside sync-budget_test.sh) that each caller's job_timeout_minutes equals its timeout-minutes, plus a paired # keep == timeout-minutes above comment at each of the 3 sites.

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.

Added three cases to sync-budget_test.sh (tier1/tier2/tier3-shard) asserting each caller's job_timeout_minutes equals that job's timeout-minutes, plus paired comments at all three sites — mutation-checked: drifting one literal to 19 fails the matching case. cfb4961.

Comment thread docs/contributor/tests.md Outdated
In CI, the `kwok-test` action derives `KWOK_SYNC_DEADLINE_EPOCH` in its
first step — before toolchain setup and the `aicr` build, so the anchor
sits within ~60 s of job start — from its `job_timeout_minutes` input
(default `18`, kept in sync with the calling job's `timeout-minutes`)

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 — Docs claim job_timeout_minutes has "default 18" — but the action declares it required: true with no default

The prose says the deadline is derived "from its job_timeout_minutes input (default 18…)". But the second commit flipped action.yml to required: true with no default and a description that explicitly forbids a stale default — the docs still describe the exact behavior the action was redesigned to forbid. A contributor reading this will believe omitting the input is safe; it isn't (the empty string fails the ^[0-9]+$ regex). It fails loudly, which is why this is Minor and not higher.

Blast radius: Doc-only inaccuracy; also misrepresents the deliberate no-default design rationale. The same wording appears in the PR body — worth correcting there too for the permanent record.

Fix: Drop "default 18"; state the input is required, integer, and must match the caller's timeout-minutes (currently 18 for the KWOK jobs).

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.

Reworded in cfb4961: required, integer, must match the caller's timeout-minutes (currently 18). Corrected the same claim in the PR description.

Comment thread .github/actions/kwok-test/action.yml Outdated
echo "::error::job_timeout_minutes must be an integer, got '${JOB_TIMEOUT_MINUTES}'"
exit 1
fi
KWOK_SYNC_DEADLINE_EPOCH=$(( $(date +%s) + JOB_TIMEOUT_MINUTES * 60 - 240 ))

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 — Integer guard ^[0-9]+$ admits zero-padded values → octal misparse

The regex accepts leading zeros, but the arithmetic $(( … + JOB_TIMEOUT_MINUTES*60 − 240 )) reads them in base-8. 010 → 8 (deadline silently too short, computed with no error — the exact silent-miscompute class this PR fights); 08/09value too great for base, which under the composite step's set -eo pipefail aborts the step before the friendly ::error:: line fires.

Blast radius: Latent — all three callers pass 18 — but the validation block's whole job is to reject bad input loudly, and for zero-padded inputs it doesn't.

Fix: Tighten to ^[1-9][0-9]*$, or force base-10: $(( … + 10#${JOB_TIMEOUT_MINUTES}*60 − 240 )).

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.

Tightened to ^[1-9][0-9]*$ with a clearer error, and forced base-10 (10#) at both arithmetic sites. cfb4961.

local sync_timeout="${KWOK_FLUX_SYNC_TIMEOUT:-500}s"
log_info "Flux sync timeout: ${sync_timeout}"
local budget_seconds
if ! budget_seconds=$(compute_sync_budget "${KWOK_FLUX_SYNC_TIMEOUT:-500}"); 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 — "Always finishes before the kill" is mildly overstated — the budget is per-chainsaw-operation, not a whole-test wall bound

The single derived budget is passed as both --assert-timeout and --error-timeout, which chainsaw applies per operation. The flux gate has ~4 sequential asserts; a chain of slow-but-eventually-passing steps can accumulate N×budget and overrun the deadline.

Blast radius: Pre-existing (identical per-op mechanics with the old fixed 480/500s) and low-probability (first timed-out step aborts the test; KWOK resources go ready-fast-or-stuck). A wording caveat, not a defect.

Fix: Note in the sync-budget.sh header that the budget is per-operation — the guarantee holds for a single stuck step, not N slow-passing ones.

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.

Softened in cfb4961 — the overstated claim actually lived in sync-budget.sh's header comment and the action description (validate-scheduling.sh sources the lib); both now state the per-operation bound rather than a whole-test wall guarantee.

Comment thread .github/actions/kwok-test/action.yml Outdated
fi
KWOK_SYNC_DEADLINE_EPOCH=$(( $(date +%s) + JOB_TIMEOUT_MINUTES * 60 - 240 ))
echo "KWOK_SYNC_DEADLINE_EPOCH=${KWOK_SYNC_DEADLINE_EPOCH}" >> "$GITHUB_ENV"
echo "KWOK_SYNC_DEADLINE_EPOCH=${KWOK_SYNC_DEADLINE_EPOCH} (job_timeout_minutes=${JOB_TIMEOUT_MINUTES}, margin=240s)"

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 — Derive step logs the deadline epoch but not the expected-kill offset

If pre-derive drift ever exceeds 240s (unlikely — shallow checkout), the deadline lands past the kill and the mechanism silently self-disables. The step already logs the epoch/minutes/margin, so it's mostly observable; what's missing is a comparison against the expected kill time to make the degenerate case greppable.

Blast radius: Observability nit only; low probability given shallow checkout and the 60s drift allowance.

Fix: Also log the expected kill epoch and warn if deadline ≥ kill.

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.

The derive step's log line now includes the implied kill offset (job kill in ~Ns) next to the deadline, so a drifted deadline is visible at a glance. cfb4961.

@ArangoGutierrez
ArangoGutierrez force-pushed the fix/kwok-sync-gate-deadline branch from cfb4961 to cae22b6 Compare July 13, 2026 07:09

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

♻️ Duplicate comments (1)
.github/actions/kwok-test/action.yml (1)

106-112: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Margin-exceeds-budget case still isn't validated — this was flagged in a prior review round and appears unaddressed.

The step validates job_timeout_minutes is a positive integer, but never checks that job_timeout_minutes*60 - 240 >= SYNC_BUDGET_FLOOR_SECONDS (120). If a future caller wires a small job_timeout_minutes (or the 240s margin is later raised) without correspondingly raising this floor, KWOK_SYNC_DEADLINE_EPOCH computes to an already-past or barely-positive value here, but the failure only surfaces later — after Go setup, kind prep, and make build have already burned CI minutes — when the first sync gate hits compute_sync_budget's floor check.

All three current callers pass 18, so nothing breaks today, but the gap that was previously called out (fail fast on an unworkable margin, right after the input format check) is still open in this version of the file.

🛡️ Proposed fix to fail fast on an unworkable margin
         if ! [[ "${JOB_TIMEOUT_MINUTES}" =~ ^[1-9][0-9]*$ ]]; then
           echo "::error::job_timeout_minutes must be a positive integer, no leading zeros, got '${JOB_TIMEOUT_MINUTES}'"
           exit 1
         fi
+        if (( 10#${JOB_TIMEOUT_MINUTES} * 60 - 240 < 120 )); then
+          echo "::error::job_timeout_minutes=${JOB_TIMEOUT_MINUTES} leaves no usable budget after the 240s diagnostics margin"
+          exit 1
+        fi
         KWOK_SYNC_DEADLINE_EPOCH=$(( $(date +%s) + 10#${JOB_TIMEOUT_MINUTES} * 60 - 240 ))
🤖 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/actions/kwok-test/action.yml around lines 106 - 112, After the
positive-integer validation for JOB_TIMEOUT_MINUTES, add a fail-fast check that
verifies the computed timeout budget (JOB_TIMEOUT_MINUTES*60 minus the
240-second margin) is at least SYNC_BUDGET_FLOOR_SECONDS (120). Emit a clear
GitHub Actions error and exit nonzero when this constraint fails, before
calculating or exporting KWOK_SYNC_DEADLINE_EPOCH.
🤖 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.

Duplicate comments:
In @.github/actions/kwok-test/action.yml:
- Around line 106-112: After the positive-integer validation for
JOB_TIMEOUT_MINUTES, add a fail-fast check that verifies the computed timeout
budget (JOB_TIMEOUT_MINUTES*60 minus the 240-second margin) is at least
SYNC_BUDGET_FLOOR_SECONDS (120). Emit a clear GitHub Actions error and exit
nonzero when this constraint fails, before calculating or exporting
KWOK_SYNC_DEADLINE_EPOCH.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: b253e3f2-4731-4462-85e0-9dc86edd43ce

📥 Commits

Reviewing files that changed from the base of the PR and between cfb4961 and cae22b6.

📒 Files selected for processing (7)
  • .github/actions/kwok-test/action.yml
  • .github/workflows/kwok-recipes.yaml
  • .github/workflows/kwok-tier3-shard.yaml
  • docs/contributor/tests.md
  • kwok/scripts/lib/sync-budget.sh
  • kwok/scripts/lib/sync-budget_test.sh
  • kwok/scripts/validate-scheduling.sh

@ArangoGutierrez
ArangoGutierrez force-pushed the fix/kwok-sync-gate-deadline branch from cae22b6 to a30bb2e Compare July 13, 2026 15:31
KWOK tier jobs (timeout-minutes: 15) raced the chainsaw sync gates'
fixed budgets (flux 500s, argocd 480s): GitHub killed the job ~1min
before chainsaw would time out and print its catch-block diagnostics,
so failures surfaced as CANCELLED with zero error report (e.g. job
86181256207). The budget was over-committed even on the good path:
~300s setup + 500s gate + ~180s post-gate diagnostics/artifacts > 900s.

Fix, in four parts:
- kwok/scripts/lib/sync-budget.sh: compute_sync_budget derives each
  gate budget as min(default, KWOK_SYNC_DEADLINE_EPOCH - now) with a
  120s floor below which the gate fails fast (existing exit code 50)
  with an explicit 'setup consumed the budget' error. Unit harness
  sibling is wired into the discover job. Local runs (env unset) keep
  today's fixed budgets.
- kwok/scripts/validate-scheduling.sh: both the argocd and flux sync
  gates consume the helper; existing KWOK_*_SYNC_TIMEOUT overrides
  still feed the default side; the derived budget is logged every run.
- .github/actions/kwok-test: new job_timeout_minutes input (default
  18); the deadline is derived in the action's FIRST step (before
  toolchain setup and the aicr build, so pre-action drift stays within
  the 240s margin's 60s allowance) and exported via GITHUB_ENV; the
  input is integer-guarded.
- Workflows: tier timeout-minutes 15 -> 18 (tier1/tier2/tier3-shard),
  since 18m fits setup + full gate + diagnostics margin with slack;
  each kwok-test call site passes job_timeout_minutes explicitly.

Verified live: full flux-oci lane passes with an ample deadline
(gate logs its full 500s budget); a nearly-consumed deadline produces
the explicit fail-fast error instead of a silent CANCELLED.

docs/contributor/tests.md documents the KWOK_SYNC_DEADLINE_EPOCH
contract.

Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
All three call sites already pass it explicitly; a default that drifts
from a caller's timeout-minutes would silently re-create the
CANCELLED-without-diagnostics failure this mechanism prevents. A missing
value now fails the derive step's integer validation loudly.

Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Adds a sync-budget_test.sh case asserting each caller's job_timeout_minutes matches its timeout-minutes (drift silently re-creates the CANCELLED-without-diagnostics failure); tightens the integer guard against zero-padded octal misparse and forces base-10 arithmetic; corrects the stale 'default 18' doc claim; per-operation budget wording; derive step logs the implied kill offset.

Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
The derive step validated job_timeout_minutes as a positive integer but
never checked that the resulting sync-gate budget (job_timeout_minutes*60
- 240s margin) clears the SYNC_BUDGET_FLOOR_SECONDS floor consumed by
compute_sync_budget. A misconfigured caller (small job_timeout_minutes, or
a future bump to the 240s margin) would compute an already-past or
barely-positive deadline that only surfaced at the first sync gate — after
Go setup, kind prep, and make build had already burned CI minutes.

Add the same floor check at derive time so misconfiguration fails before
any of that work starts. The 120 literal is hand-synced with
SYNC_BUDGET_FLOOR_SECONDS in kwok/scripts/lib/sync-budget.sh and is
guarded by sync-budget_test.sh, same contract as the existing 240s margin
sync check.

Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
@ArangoGutierrez
ArangoGutierrez force-pushed the fix/kwok-sync-gate-deadline branch from a30bb2e to 62f2268 Compare July 13, 2026 15:33

@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/sync-budget_test.sh`:
- Around line 73-99: Replace the indent-sensitive regex parsing in
job_timeout_sync with yq-based YAML introspection, using job names and their
timeout-minutes and job_timeout_minutes values directly from the workflow
structure. Preserve the existing output format of job:jtm=...,tm=... and the
current synchronization checks, while allowing normal YAML reformatting and
inline comments.
🪄 Autofix (Beta)

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: 1e7c686c-55e4-4d85-b6ee-95baf5aa5dd3

📥 Commits

Reviewing files that changed from the base of the PR and between cae22b6 and a30bb2e.

📒 Files selected for processing (7)
  • .github/actions/kwok-test/action.yml
  • .github/workflows/kwok-recipes.yaml
  • .github/workflows/kwok-tier3-shard.yaml
  • docs/contributor/tests.md
  • kwok/scripts/lib/sync-budget.sh
  • kwok/scripts/lib/sync-budget_test.sh
  • kwok/scripts/validate-scheduling.sh

Comment on lines +73 to +99
job_timeout_sync() {
local file="$1" job="" job_timeout=""
while IFS= read -r line; do
if [[ "${line}" =~ ^\ \ ([A-Za-z0-9_-]+):[[:space:]]*$ ]]; then
job="${BASH_REMATCH[1]}"
job_timeout=""
continue
fi
if [[ "${line}" =~ ^\ \ \ \ timeout-minutes:\ *([0-9]+)[[:space:]]*$ ]]; then
job_timeout="${BASH_REMATCH[1]}"
continue
fi
if [[ "${line}" =~ job_timeout_minutes:\ *\'?([0-9]+)\'? ]]; then
echo "${job}:jtm=${BASH_REMATCH[1]},tm=${job_timeout:-unset}"
fi
done < "${file}"
}

kwok_recipes_out=$(job_timeout_sync "${REPO_ROOT}/.github/workflows/kwok-recipes.yaml")
check "kwok-recipes-tier1-job-timeout-in-sync" 0 "test-tier1:jtm=18,tm=18" \
0 "$(echo "${kwok_recipes_out}" | grep '^test-tier1:')"
check "kwok-recipes-tier2-job-timeout-in-sync" 0 "test-tier2:jtm=18,tm=18" \
0 "$(echo "${kwok_recipes_out}" | grep '^test-tier2:')"

tier3_shard_out=$(job_timeout_sync "${REPO_ROOT}/.github/workflows/kwok-tier3-shard.yaml")
check "kwok-tier3-shard-job-timeout-in-sync" 0 "test:jtm=18,tm=18" \
0 "$(echo "${tier3_shard_out}" | grep '^test:')"

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.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Regex-based YAML parsing in job_timeout_sync is indent-sensitive and will silently stop matching on reformatting.

The parser depends on exact 2-space job-key indent and exact 4-space timeout-minutes indent (Lines 76, 81). A future reformat (e.g. a trailing inline comment on timeout-minutes: 18 # ..., or re-indentation) would make job_timeout fall back to "unset" without any parse error — silently defeating the exact literal-sync guard this test exists to enforce. yq is already a required repo tool (per DEVELOPMENT.md's tools table) and is used elsewhere in this same repo's workflows for YAML introspection; using it here would remove the indent-coupling.

Not blocking — current behavior is correct and mutation-tested — but worth hardening given this guard's whole purpose is catching silent drift.

🤖 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/sync-budget_test.sh` around lines 73 - 99, Replace the
indent-sensitive regex parsing in job_timeout_sync with yq-based YAML
introspection, using job names and their timeout-minutes and job_timeout_minutes
values directly from the workflow structure. Preserve the existing output format
of job:jtm=...,tm=... and the current synchronization checks, while allowing
normal YAML reformatting and inline comments.

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

Solid CI reliability fix — deadline-derived sync budgets with a fail-fast floor turn silent CANCELLED-with-no-diagnostics into an explicit exit-50 report. Logic is correct, boundaries between action.yml and sync-budget.sh are consistent (jtm=6 ↔ 120s floor), the deadline is anchored early so setup drift is accounted for, and the test harness extracts CI logic from source rather than duplicating it. One low nit inline: the new required job_timeout_minutes input isn't reflected in .github/actions/README.md. Nothing blocking.

diagnostics - before GitHub kills the job in the expected
single-gate-dominates case (this bounds each gate operation, not the
job's overall wall time). Required with no default: every caller
must wire its own timeout-minutes here, so a job that changes its

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.

nit: job_timeout_minutes is now a required input, but .github/actions/README.md (the kwok-test input catalog + usage example) wasn't updated — its Inputs list omits this input and the example would fail the integer guard if copy-pasted. Worth adding in this PR.

@mchmarny
mchmarny merged commit 079e173 into NVIDIA:main Jul 13, 2026
160 checks passed
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.

3 participants