nodewright-packages
AGENTS.md10 starsChanged 52 days agoApache-2.0View on GitHub
The canonical file is AGENTS.md at the repo root. CLAUDE.md is a symlink to it, so agents that look for either name find the same content. Keep edits in AGENTS.md. This repo holds the pre-built packages consumed by the NVIDIA NodeWright operator (the operator repo is NVIDIA/nodewright, also known as skyhook). A package is a container image that the operator runs on a node to install, configure, tune, or clean up host-level software through a defined lifecycle. This repo does…
- Reads credentials
- Deletes or force-pushes
- Commits and pushes
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,542 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repositoryOne other file it gives its agents.
- .github/copilot-instructions.mdCopilot instructions
The file
Show its rules and text
We split this AGENTS.md into the rules it gives an agent
13 rules. About 0 are common, 3 are distinctive, and 10 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
10
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Very specific: line 56
Keep these four in sync with each other.
- Very specific: line 72
Run from the repo root unless noted.
- Very specific: line 85
Sign-off and signing are both required: Use git commit -s -S: -s adds the DCO Signed-off-by trailer, -S the GPG/SSH signature.
- Distinctive: line 91
Run the checks CI runs, on your machine, before you push.
- Very specific: line 117
Found a committed secret? Stop and report it: Do not quietly delete it in a follow-up commit: the value survives in git history and has to …
- Very specific: line 130
Strict mode: start scripts with set -euo pipefail so they fail fast on errors, unset variables, and broken pipes.
- Very specific: line 149
Guidance docs: add new repo-guidance files to knowledge_base.code_guidelines.filePatterns (currently AGENTS.md, CONTRIBUTING.md, README.md, …
- Very specific: line 150
Generated or vendored content: add new such paths to reviews.path_filters (currently the git-cliff-generated CHANGELOG.md and …
- Very specific: line 151
Tools: keep reviews.tools aligned with the linters we actually use.
- Distinctive: line 155
Prefer the latest stable versions of the software this repo depends on, and keep them moving forward rather than letting them drift.
- Distinctive: line 166
When you touch a file that references a pinned action, a base image tag, or a tool version, check whether a newer stable release exists and …
- Very specific: line 173
Do not add a config.json to an inherited package unless it genuinely needs its own.
- Very specific: line 174
Test directory names use underscores while package directories use hyphens (nvidia-setup to tests/integration/nvidia_setup).
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
<!--
SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
-->CLAUDE.md / AGENTS.md
This file provides guidance to coding agents (Claude Code, Cursor, Codex, etc.) when working in this repository.
The canonical file is AGENTS.md at the repo root. CLAUDE.md is a symlink to it, so agents that look for either name find the same content. Keep edits in AGENTS.md.
Repository overview
This repo holds the pre-built packages consumed by the NVIDIA NodeWright operator (the operator repo is NVIDIA/nodewright, also known as skyhook). A package is a container image that the operator runs on a node to install, configure, tune, or clean up host-level software through a defined lifecycle.
This repo does not contain the operator, the agent, or any Go code. It is shell scripts (could be python, or any executable, but currently is all shell), Dockerfiles, package config, and Python-based tests/tooling.
Rename status (Skyhook to NodeWright): the project is being renamed from Skyhook to NodeWright. New package releases publish at ghcr.io/nvidia/nodewright-packages/...; older package versions may still exist only under the legacy ghcr.io/nvidia/skyhook-packages/... namespace. The CRD group (skyhook.nvidia.com/v1alpha1), the on-host SKYHOOK_DIR/skyhook_dir names, and the CLI still use skyhook on purpose. The GitHub repo itself is now nodewright-packages (with skyhook-packages redirecting). Do not rename remaining skyhook references without checking their specific compatibility contract.
Repository layout
- Each top-level directory that contains a
Dockerfileis a package. Current packages:shellscript,tuning,tuned,kdump,nvidia-setup,nvidia-tuning-gke,nvidia-tuned,copy-fail,bind-mount. scripts/: repo tooling (validate.py,sync_labels.py).tests/integration/: Docker-based pytest suites, one subdir per package that has tests (with hyphens converted to underscores, e.g.nvidia-setuptonvidia_setup; not every package has a subdir)..github/: workflows, thebuild-packagecomposite action, issue/PR templates,labels.yml,CODEOWNERS.docs/: two tracked references,docs/release-process.mdanddocs/artifact-locations.md(which versions of each package image live under which registry path).docs/plansanddocs/superpowersare gitignored working areas. Other authoritative prose lives in the root*.mdfiles below.
Package internal structure
<package>/
├── Dockerfile # copies package contents to /skyhook-package in the image
├── config.json # package manifest, validated against the agent schema
├── README.md # per-package usage docs
├── root_dir/ # files copied verbatim to the host root: root_dir/foo -> /foo
└── skyhook_dir/ # lifecycle scripts and static files the scripts useAdding a new package
A package becomes "real" to CI once its directory has a Dockerfile. Beyond the
package contents themselves, a few repo-wide places track the package set and must
be updated in the same change, or its label automation silently goes stale:
.github/labels.yml: add apackage/<name>label (same color family as the others). Runmake labelsto sync it to GitHub..github/labeler.yml: add apackage/<name>entry mapping<name>/**so PRs touching the package get labeled..github/ISSUE_TEMPLATE/bug_report_form.ymlandfeature_request_form.yml: add<name>to the "Which package does this relate to?" dropdown so reporters can select it..github/workflows/triage.yaml: add<name>to thePACKAGESlist (keep the longest-name-first ordering so substring names liketuneddon't shadownvidia-tuned).
Keep these four in sync with each other; the dropdown value, the label suffix, the
labeler glob directory, and the triage PACKAGES entry are all the same package
directory name.
How packages work (the contract you must not break)
- Everything under
/skyhook-packagein the image lands at${SKYHOOK_DIR}on the host.skyhook_dir/lands at${STEP_ROOT}.root_dir/is laid down at/. config.jsonmust comply with the skyhook agent schemas v1. It declaresschema_version,package_name,package_version(semver), and amodesmap that points each lifecycle stage at a script, arguments, accepted return codes,on_host, andidempotence.- Runtime metadata available to scripts:
STEP_ROOT,SKYHOOK_DIR,${SKYHOOK_DIR}/configmaps(the package's configmaps), and${SKYHOOK_DIR}/node-metadata/(labels.json,annotations.json,packages.json). - Lifecycle stages (see
PACKAGE_LIFECYCLE.mdfor the full contract):apply,config,interrupt,post-interrupt,upgrade,uninstall. Every action stage has a paired-checkmode that validates the action succeeded. Stages must be idempotent.- Normal flow:
uninstall -> apply -> config -> interrupt -> post-interrupt - Upgrade flow:
upgrade -> config -> interrupt -> post-interrupt
- Normal flow:
- Inherited packages build
FROManother package's published image (theFROMline containsskyhook-packagesornodewright-packages). Examples:nvidia-tunedinherits fromtuned,nvidia-tuning-gkefromtuning. An inherited package may omitconfig.jsonif it does not need its own; a standalone package must have one.
Working in this repo: do this before committing
Run from the repo root unless noted. See DEVELOPER.md for the full pre-commit checklist.
- License headers: source files (
*.py,*.sh,*.yaml/*.yml,Dockerfile) carry the full Apache 2.0 block header from.github/license-header.tmpl; Markdown and other docs do not need one. Runmake license-fmtto add/refresh headers andmake license-checkto verify (theLicense Headersworkflow runs the check on every PR and also fails on a duplicated header). Both wrapgoogle/addlicenseviago run, so they need a local Go toolchain; addlicense is idempotent and never duplicates a header (it replaced the oldformat_license.py, which did, see #74). - Validate config:
make validate-standalone PACKAGE=<name>for standalone packages, ormake validate-inherited PACKAGE=<name>for inherited ones (this builds the image first, then validates the assembled/skyhook-package). Validation runs in theghcr.io/nvidia/skyhook/agent:latestcontainer and needs Docker or Podman. - Test:
make testruns the whole Docker-based suite in parallel;make test-package PACKAGE=<name>runs one package's tests. Tests need a working Docker daemon and create avenv/. - Changelog (per package):
<package>/CHANGELOG.mdis generated from Conventional Commit history bygit-cliff(seecliff.toml); do not hand-edit it in a PR. Usemake changelog-preview PACKAGE=<name>to preview the unreleased notes your commits will produce. The file is (re)written bymake changelog PACKAGE=<name>at release time (scoped by--include-path <package>/**and--tag-pattern <package>/.*), so a PR that touchesCHANGELOG.mdwill conflict with that regeneration and should be reverted. <package>/RELEASE_NOTES.mdis not a changelog, and most PRs do not belong in it. At release,release.ymlprepends the section for that version to the GitHub Release body, on top of the generated changelog that already records every change. Add a hand-written section only when the reader has to do something or will be surprised: an action required at upgrade time, a deprecation with a deadline, a behavior they will notice and did not ask for, a new knob worth discovering. A bug fix that restores intended behavior needs no entry, however involved the fix was. Default to not adding one; the commit message and the linked issue carry the reasoning.- Labels:
make labelssyncs.github/labels.ymlto GitHub (needsghwith write access).
Commit and version conventions
- Conventional Commits, with the package name as scope:
feat(shellscript): ...,fix(tuning): .... This applies to both commit messages and PR titles: a squash-merge uses the PR title as the commit subject. Usegeneral(orgeneral/ci) as the scope for repo-wide or CI changes, and use exactly one type per message; combined types likedocs+ciare not valid Conventional Commits. - CC is enforced in CI.
.github/workflows/commit-linting.yamlruns commitlint (@commitlint/config-conventional, configured incommitlint.config.js) against the PR title and every commit in the PR, so an invalid type, a non-CC subject, or a combined type likedocs+cifails the check. The subject-case rule is relaxed so capitalized subjects pass, and scope is not restricted to an enum. Lint locally withecho "type(scope): subject" | npx @commitlint/cli@21 --config commitlint.config.js(npx fetches commitlint if it is not installed); CI is the source of truth. - Sign-off and signing are both required. Use
git commit -s -S:-sadds the DCOSigned-off-bytrailer,-Sthe GPG/SSH signature..github/workflows/commit-requirements.yamlcomments on PRs with a commit missing either (advisory, not yet a required check); bot commits are exempt and merge commits need only the signature. - Semantic versioning per package. Bump
package_versionin the package'sconfig.json, then tag as<package>/<version>(e.g.tuned/1.3.0). Each package versions independently. - Force-pushing a PR branch. Review fixes may be folded into the existing commit (
git commit --amend -s -S --no-edit) and force-pushed. Always push withgit push --force-with-lease --force-if-includes, never a bare--force, which overwrites whatever is on the remote without looking. Astale inforejection means the branch moved since your last fetch (a maintainer's "Update branch", a co-author's commit, a bot fixup): fetch, find out what moved, and integrate it. It is not a cue to escalate to--force.
Before you open a pull request
Run the checks CI runs, on your machine, before you push. This applies to a coding agent exactly as it does to a person.
- Run the targets that match what you changed. A package:
make validate-standalone PACKAGE=<name>(ormake validate-inherited PACKAGE=<name>) plusmake test-package PACKAGE=<name>. The shared harness undertests/helpers/:make test-harness. Any*.py,*.sh,*.yaml,*.ymlorDockerfile:make license-check. A changed shell script:shellcheckon it. A changed workflow:actionlint. CI invokes those same make targets, so this is the gate rather than an approximation of it. A docs-only change needs none of it. - Supply the two prerequisites the Makefile does not. Everything except
make license-checkneeds a running container runtime, and the test harness talks to Docker specifically (docker-pyplus adocker buildcall), so Podman needs a Docker-compatible socket and adockercommand.make license-fmtandmake license-checkneed a local Go toolchain. The Python test dependencies are installed for you intovenv/, so a missing pytest means a target was bypassed. A tool you do not have is a fact to report in the pull request, never a reason to claim a suite passed. - A package change is not verified until it has run on a real node. CI runs lifecycle scripts inside a container; it cannot touch bootloaders, tuned profiles, drivers or disks, which is most of what these packages do. The pull request must say which OS and version, which hardware (GPU model and driver version where the change depends on them), how the package was applied, and what was observed on the host before and after. You have no hardware, so say exactly that rather than writing anything that implies the change was exercised.
- On a fork this is the fast path, not the slow one. Workflow runs from a fork wait for a maintainer to approve them by hand, so pushing to see what CI says costs hours where the same checks locally cost minutes.
- Every pull request links an issue assigned to its author. Put
closes #Nin the PR body, and make sure the issue is assigned to the person the work is for before writing any code for it: comment/assign, or rungh issue edit N --add-assignee @mewhen it is unassigned. If the issue is already assigned to someone else, do not start; tell the user. The only exceptions are trivial changes (typos, broken links, comment fixes), Dependabot updates, and security fixes, which go throughSECURITY.mdand must never reference the non-public report. This applies to maintainers too. - Work-in-progress limit: three open PRs and three assigned issues per contributor, maintainers included. Draft PRs count; Dependabot PRs do not, and neither do issues typed Epic or Initiative (their sub-issues count instead). The
/assignbot enforces the issue half and refuses a fourth claim. Before opening a PR or claiming an issue, checkgh pr list --author @me --state openandgh issue list --assignee @me --state open --search "-type:Epic -type:Initiative". At three, stop and tell the user instead of opening or claiming a fourth. Release an issue you are no longer working on with/unassign. - Stay with the pull request after you open it. A review costs a maintainer time whether or not anyone answers it, so respond to comments and rebase when asked. Inactive pull requests are nudged at 7 days, marked stale at 14, and closed 7 days later.
- Write what you ran, what you could not, and what it ran on into the pull request body, along with AI assistance if a tool wrote a meaningful part of the change. The rest of the process is in
CONTRIBUTING.md.
Issue and PR labels
.github/labels.ymlis the closed set of labels. It is the only source of truth, synced to GitHub withmake labels. Because that sync creates and updates but never deletes, GitHub may carry undeclared extras; treat any label not defined in the file as one that should not exist, never as precedent for adding more.- Never create a label. No
gh label create, nogh issue create --label <something-new>, no label creation throughgh api. A label invented once lingers on the repo until someone deletes it by hand. If a label you want is genuinely missing, add it to.github/labels.ymlin a PR and stop there. - Priority is not a label. It is a native GitHub issue Field (Urgent / High / Medium / Low) in the issue sidebar. Maintainers set it during triage; agents and issue reporters do not set it at all. Never apply
priority/*,P0/P1/P2, or any severity label, and never write the Priority field from automation. If you think something is urgent, say so in the issue body and let a maintainer decide. - When filing an issue, apply no labels yourself. The issue forms add their own type label,
.github/workflows/triage.yamladdsneeds-triageand inferspackage/*, and.github/labeler.ymllabels PRs by path. Let that automation run rather than pre-labeling.
Secrets and credentials
Hard boundaries: violating one means a rotation, not a revert.
- Never commit credentials, API keys, tokens, passwords, kubeconfigs, or private keys. Nothing secret belongs in a tracked file: not in a package's
skyhook_dir/scripts orroot_dir/files, not inconfig.json, not in aDockerfileor anything it copies into an image layer, not in fixtures undertests/, not in.github/workflows/, and not in a commit message. Examples use an obvious placeholder (REPLACE_ME,<your-token>), never a real value that happens to be expired. - Never read a real credential into your output.
~/.kube/config,~/.docker/config.json,~/.ssh/,~/.aws/, and any.envare off limits: refer to them by path, never print them into the transcript, a file, or a PR description. - Secret-bearing environment variables stay out of tracked files. CI reads them via
${{ secrets.* }}; local runs read them from your shell. Do not add a.envwith real values, and never give a secret-bearing variable a hardcoded fallback default. - Packages receive secrets at runtime, never baked in. A package that needs a credential reads it from an environment variable that the package's
envin the Skyhook CR sources from a Kubernetes Secret (secretKeyRef); see providing secrets in the operator repo. Never put one in an image layer orconfig.json, never document putting one in the package's configmap (a ConfigMap is not a Secret, and its contents land as plain files under${SKYHOOK_DIR}/configmaps), and never echo it or trace it withset -xin a lifecycle script. - Found a committed secret? Stop and report it. Do not quietly delete it in a follow-up commit: the value survives in git history and has to be rotated regardless. Report it through
SECURITY.md; do not open a public issue.
Writing style
For prose you author in this repo (docs, READMEs, PR descriptions, commit messages, review comments): do not use em-dashes (—). Use a colon, semicolon, comma, or full stop instead, and recast the sentence when no punctuation swap reads cleanly. Leave intact things that are not authored prose: en-dashes in numeric ranges (24–27), markdown horizontal rules (---), and any em-dash already inside quoted text or code.
Keep markdown well-formed: a single # H1 per file with headings nested in order (no skipped levels), fenced code blocks that carry a language hint (```bash, ```yaml), blank lines around headings, lists, and code fences, consistent list markers, tables with aligned pipes and a header separator row, and links/relative paths that actually resolve. When you edit a doc, preview the rendered markdown rather than trusting the raw text.
Shell scripting conventions
Almost all package logic is bash. The existing scripts are not yet consistent (shebangs and set flags vary), so when you add or touch a script, move it toward this standard rather than matching the nearest neighbour:
- Shebang:
#!/usr/bin/env bash. - Strict mode: start scripts with
set -euo pipefailso they fail fast on errors, unset variables, and broken pipes. Lifecycle-checkscripts are the deliberate exception where a non-zero exit is the signal, not a crash; handle expected failures explicitly there. - Quote every expansion:
"${var}","$@","$(cmd)". Unquoted expansions (shellcheckSC2086) are by far the most common issue in this repo and the main thing to avoid adding. - Prefer
[[ ... ]]over[ ... ],$(...)over backticks, and declare-then-assign for command substitution (local x; x="$(cmd)") so a failing command is not masked (SC2155). - Idempotence: lifecycle scripts must be safe to re-run (see
PACKAGE_LIFECYCLE.md); either rely on the agent's idempotence tracking or write the script so repeats are no-ops. - Lint locally before pushing:
shellcheck path/to/script.sh. CI runs shellcheck across all tracked*.shfiles whenever a.shfile,.shellcheckrc, or the workflow changes (advisory today; it reports findings without blocking, and will become a required check as the count reaches zero).SC1091(can't follow runtime-sourced files like${SKYHOOK_DIR}/...) is suppressed via.shellcheckrc.
CI and release mechanics
- On PR (
pr_build.yaml): detects which package directories changed, then for each changed package runs validate, test, and a dev build tagged<version>-dev.sha<short>. Changed-package detection keys on a directory having aDockerfile;scripts/and hidden dirs are skipped. - On tag push
<package>/<version>: two independent workflows run.build_container.yamlbuilds and pushes the multi-arch image (linux/amd64,linux/arm64) with a build-provenance attestation;release.ymlcreates the GitHub Release withgit-cliffnotes scoped to that package. The image build (here and inpr_build.yaml) goes through the.github/actions/build-packagecomposite action. (build_package.ymlis a reusableworkflow_callworkflow that exists but is not currently wired up.) - Image tags: the image is tagged with the version exactly as written in the git tag, so
tuned/1.4.0publishestuned:1.4.0andtuned/1.4.0-rc.1publishestuned:1.4.0-rc.1. A stable release also moveslatest; a release candidate does not. Any other version form fails the build, matching whatrelease.ymlaccepts. - Published image path for new releases:
ghcr.io/nvidia/nodewright-packages/<package>:<version>. Verify legacy versions before changing an existing consumer because some older tags remain only underghcr.io/nvidia/skyhook-packages/; docs/artifact-locations.md lists which versions are on which path. - NGC staging: after the GHCR build, the
publish-ngcjob inbuild_container.yamlcopies the image by digest tonvcr.io/1070195845945670/nodewright-packages/<package>:<version>, from where NVIDIA's internal release process promotes it to the publicnvcr.io/nvidiacatalog. It copies withoras cp -r, so the keyless signatures (index and every platform manifest) and the provenance attestation come along unchanged; the job fails if the NGC digest differs from GHCR, then runs the samecosign verifychecks against the copy. It runs in thengc-publishenvironment, which holdsNGC_API_KEY(a service key scoped to thenodewright-packagesNGC team) and admits only<package>/<version>tags. An NGC failure leaves the GHCR release intact and can be re-run on its own. - An optional
<package>/preprocess.shis run before the image build and can emitBUILD_ARGS.
Code review (CodeRabbit)
.coderabbit.yaml configures CodeRabbit's automated PR review. Keep it in step with the repo so reviews stay useful, and update it in the same change that introduces the drift:
- Guidance docs: add new repo-guidance files to
knowledge_base.code_guidelines.filePatterns(currentlyAGENTS.md,CONTRIBUTING.md,README.md,DEVELOPER.md,PACKAGE_LIFECYCLE.md) so reviews enforce our conventions. - Generated or vendored content: add new such paths to
reviews.path_filters(currently the git-cliff-generatedCHANGELOG.mdandRELEASE_NOTES.md) so CodeRabbit does not review non-authored files. - Tools: keep
reviews.toolsaligned with the linters we actually use.shellcheckis on and shares rules with CI via.shellcheckrc; revisityamllint(currently off) if malformed YAML starts slipping through.
Keeping software current
Prefer the latest stable versions of the software this repo depends on, and keep
them moving forward rather than letting them drift. This covers base images in
Dockerfiles, the tools the lifecycle scripts invoke, the Python deps under
tests/, and the uses: actions in workflows. Staying current is how we pick up
upstream security and bug fixes.
Balance "latest" against reproducibility and supply-chain safety:
- Pin GitHub Actions to a commit SHA with the human-readable version in a
trailing comment (e.g.
actions/labeler@f27b...e213 # v6.1.0). To update, bump the SHA and the comment together; do not float a@v6tag. - When you touch a file that references a pinned action, a base image tag, or a tool version, check whether a newer stable release exists and update it if the bump is safe. Treat a stale pin you are already editing as worth refreshing.
Gotchas
- A new package is "real" once it has a
Dockerfile; that is what CI keys on. - Do not add a
config.jsonto an inherited package unless it genuinely needs its own; CI treats a missingconfig.jsonas valid only when theDockerfileinherits from askyhook-packages/nodewright-packagesimage. - Test directory names use underscores while package directories use hyphens (
nvidia-setuptotests/integration/nvidia_setup). docs/plansanddocs/superpowersare gitignored working areas, not shipped docs.
Key references
README.md: repository and per-package overviewPACKAGE_LIFECYCLE.md: the lifecycle-stage contractDEVELOPER.md: validation and pre-commit stepsCONTRIBUTING.md: DCO sign-off, commit signing, and commit style- NodeWright operator (
NVIDIA/skyhook): the operator that runs these packages - Agent schemas v1: the
config.jsoncontract
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

