Skip to content

ci: add SPDX license header check for source files - #842

Closed
Junior00619 wants to merge 1 commit into
NVIDIA:mainfrom
Junior00619:ci/add-spdx-header-check
Closed

ci: add SPDX license header check for source files#842
Junior00619 wants to merge 1 commit into
NVIDIA:mainfrom
Junior00619:ci/add-spdx-header-check

Conversation

@Junior00619

@Junior00619 Junior00619 commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Hooks scripts/check-spdx-headers.sh into the PR workflow so missing license headers get caught in CI. The script was already wired into prek pre-commit hooks with --fix, but contributors who skip local hooks could push files without headers and nobody would notice until review.

Related Issue

Fixes #551

Changes

  • Add check-spdx-headers job to .github/workflows/pr.yaml
  • Job diffs source files (.ts, .js, .py, .sh, .yaml, .yml) against PR base
  • Excludes generated/vendored paths (node_modules/, dist/, docs/, .venv/)
  • Runs in check-only mode (no --fix) — fails with file list + expected header on missing headers
  • Skips cleanly when no matching source files are in the diff

Type of Change

  • Code change for a new feature, bug fix, or refactor.
  • Code change with doc updates.
  • Doc only. Prose changes without code sample modifications.
  • Doc only. Includes code sample changes.

Testing

  • npx prek run --all-files passes (or equivalently make check).
  • npm test passes.
  • make docs builds without warnings. (for doc-only changes)

Ran check-spdx-headers.sh locally against existing source files — all pass. Confirmed it correctly rejects a test file without the header (exits 1, prints filename).

Checklist

General

Code Changes

  • Formatters applied — npx prek run --all-files auto-fixes formatting (or make format for targeted runs).
  • Tests added or updated for new or changed behavior.
  • No secrets, API keys, or credentials committed.
  • Doc pages updated for any user-facing behavior changes (new commands, changed defaults, new features, bug fixes that contradict existing docs).

Summary by CodeRabbit

  • Tests
    • Added a CI check to validate SPDX-style file headers in source contributions.
    • The check targets typical code files, ignores generated/build/vendor directories, and prints expected header text before running validation.
    • The validation is skipped automatically when no relevant files are changed to avoid unnecessary blocking.

@coderabbitai

coderabbitai Bot commented Mar 25, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds a GitHub Actions job check-spdx-headers to pr workflow that detects changed source files (TS/JS/MJS/CJS/PY/SH/YAML), excludes generated or vendored paths, and conditionally runs scripts/check-spdx-headers.sh to validate SPDX license headers when matches exist.

Changes

Cohort / File(s) Summary
SPDX Header Validation
\.github/workflows/pr.yaml
New check-spdx-headers job: checks out full history, computes changed files between PR base and HEAD, filters by extensions and exclusion patterns (dist/, build/, docs/, node_modules/, .venv/, _deps/, nemoclaw/node_modules/), sets skip=true when no files match, prints expected SPDX lines, and invokes scripts/check-spdx-headers.sh with the filtered file list.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 I hopped through branches, sniffed each file,

Lines of license made me smile,
A CI job now guards the gate,
Headers tidy, code feels great,
Hooray — compliance in a while!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'ci: add SPDX license header check for source files' directly and clearly describes the main change—adding a CI check for SPDX headers.
Linked Issues check ✅ Passed The PR implements all core requirements: checks SPDX headers on PR-changed source files, excludes generated/vendored paths, runs on pull_request events, fails and reports missing headers, and supports specified file types.
Out of Scope Changes check ✅ Passed The change is limited to adding a GitHub Actions job to .github/workflows/pr.yaml; it contains no unrelated modifications and directly addresses issue #551.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/pr.yaml:
- Line 180: The exclusion glob list in the CI workflow string that currently
contains
"node_modules/*|dist/*|build/*|docs/*|nemoclaw/node_modules/*|.venv/*|_deps/*"
needs to also exclude test fixture paths: add "testdata/*" and "test/fixtures/*"
to that same pattern so CI ignores those directories; update the matching
pattern (the one containing
node_modules/*|dist/*|build/*|docs/*|nemoclaw/node_modules/*|.venv/*|_deps/*) to
include these two new segments separated by |.
- Around line 173-183: The loop that builds and iterates over files uses
unquoted word-splitting (the files and filtered variables and the for f in
$files loop), which breaks on filenames with spaces; change the pipeline to use
NUL-delimited transport: call git diff -z --name-only (store to a NUL-terminated
variable or pipe), build filtered by reading entries with read -r -d '' (or
filter via while read -r -d '' f; do ...; done) and collect safely without
unquoted expansion, then pass results to the SPDX check using xargs -0 or a
NUL-aware loop instead of unquoted variable expansion so filenames containing
spaces are handled correctly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8ffd36e4-628e-4287-acf9-fcb8f7c7c80b

📥 Commits

Reviewing files that changed from the base of the PR and between bb8ba78 and 17a26e1.

📒 Files selected for processing (1)
  • .github/workflows/pr.yaml

Comment thread .github/workflows/pr.yaml
Comment on lines +173 to +183
files=$(git diff --name-only --diff-filter=ACMRT "$base" -- \
'*.ts' '*.tsx' '*.js' '*.mjs' '*.cjs' '*.py' '*.sh' '*.yaml' '*.yml' \
|| true)
# Exclude paths that are generated, vendored, or don't support comments.
filtered=""
for f in $files; do
case "$f" in
node_modules/*|dist/*|build/*|docs/*|nemoclaw/node_modules/*|.venv/*|_deps/*) continue ;;
esac
filtered="$filtered $f"
done

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the actual workflow file to see the code in context
head -250 .github/workflows/pr.yaml | tail -100

Repository: NVIDIA/NemoClaw

Length of output: 3270


🏁 Script executed:

# Also check the script being called
cat scripts/check-spdx-headers.sh

Repository: NVIDIA/NemoClaw

Length of output: 2634


🏁 Script executed:

# Check the entire pr.yaml file for context around lines 173-183, 191-193, 205
wc -l .github/workflows/pr.yaml

Repository: NVIDIA/NemoClaw

Length of output: 88


🏁 Script executed:

# Search for actual files with spaces in the repo that would match the file patterns
find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.mjs" -o -name "*.cjs" -o -name "*.py" -o -name "*.sh" -o -name "*.yaml" -o -name "*.yml" \) -print0 | xargs -0 -I {} basename {} | grep ' ' | head -10

Repository: NVIDIA/NemoClaw

Length of output: 41


🏁 Script executed:

# Also verify the lines mentioned in the review match what we see
sed -n '173,183p' .github/workflows/pr.yaml
sed -n '191,193p' .github/workflows/pr.yaml  
sed -n '205,205p' .github/workflows/pr.yaml

Repository: NVIDIA/NemoClaw

Length of output: 788


Use NUL-delimited file transport to avoid silent check failures on paths with spaces.

The workflow at lines 173–183, 191–193, and 205 uses unquoted variable expansion for file transport, which causes word-splitting. A source file with spaces in its name (e.g., my file.ts) would be split into separate arguments; the script's [[ -f "$file" ]] || continue silently skips non-existent entries, bypassing the SPDX header check without error.

The fix uses git diff -z to output NUL-separated filenames, processes them safely with read -r -d '', and transports via xargs -0:

Suggested fix
       - name: Get changed source files
         id: changed
         run: |
           base="${{ github.event.pull_request.base.sha }}"
           # File types that must carry SPDX headers.
-          files=$(git diff --name-only --diff-filter=ACMRT "$base" -- \
-            '*.ts' '*.tsx' '*.js' '*.mjs' '*.cjs' '*.py' '*.sh' '*.yaml' '*.yml' \
-            || true)
+          tmp_raw="$RUNNER_TEMP/spdx-raw-files.zlist"
+          git diff -z --name-only --diff-filter=ACMRT "$base" -- \
+            '*.ts' '*.tsx' '*.js' '*.mjs' '*.cjs' '*.py' '*.sh' '*.yaml' '*.yml' \
+            > "$tmp_raw" || true
           # Exclude paths that are generated, vendored, or don't support comments.
-          filtered=""
-          for f in $files; do
+          filtered=()
+          while IFS= read -r -d '' f; do
             case "$f" in
               node_modules/*|dist/*|build/*|docs/*|nemoclaw/node_modules/*|.venv/*|_deps/*) continue ;;
             esac
-            filtered="$filtered $f"
-          done
-          filtered="${filtered# }"
-          if [ -z "$filtered" ]; then
+            filtered+=("$f")
+          done < "$tmp_raw"
+          if [ "${`#filtered`[@]}" -eq 0 ]; then
             echo "No source files changed."
             echo "skip=true" >> "$GITHUB_OUTPUT"
           else
+            list_file="$RUNNER_TEMP/spdx-files.zlist"
+            printf '%s\0' "${filtered[@]}" > "$list_file"
             echo "skip=false" >> "$GITHUB_OUTPUT"
-            echo "$filtered"
-            echo "files<<EOF" >> "$GITHUB_OUTPUT"
-            echo "$filtered" >> "$GITHUB_OUTPUT"
-            echo "EOF" >> "$GITHUB_OUTPUT"
+            echo "file_list=$list_file" >> "$GITHUB_OUTPUT"
           fi

       - name: Check SPDX license headers
         if: steps.changed.outputs.skip != 'true'
         run: |
           echo ""
           echo "Expected header (comment style varies by language):"
           echo "  SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved."
           echo "  SPDX-License-Identifier: Apache-2.0"
           echo ""
-          # shellcheck disable=SC2086
-          bash scripts/check-spdx-headers.sh ${{ steps.changed.outputs.files }}
+          xargs -0 -r bash scripts/check-spdx-headers.sh < "${{ steps.changed.outputs.file_list }}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/pr.yaml around lines 173 - 183, The loop that builds and
iterates over files uses unquoted word-splitting (the files and filtered
variables and the for f in $files loop), which breaks on filenames with spaces;
change the pipeline to use NUL-delimited transport: call git diff -z --name-only
(store to a NUL-terminated variable or pipe), build filtered by reading entries
with read -r -d '' (or filter via while read -r -d '' f; do ...; done) and
collect safely without unquoted expansion, then pass results to the SPDX check
using xargs -0 or a NUL-aware loop instead of unquoted variable expansion so
filenames containing spaces are handled correctly.

Comment thread .github/workflows/pr.yaml
filtered=""
for f in $files; do
case "$f" in
node_modules/*|dist/*|build/*|docs/*|nemoclaw/node_modules/*|.venv/*|_deps/*) continue ;;

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.

⚠️ Potential issue | 🟠 Major

Exclusion patterns are missing required fixture/testdata paths.

Line 180 does not exclude testdata/ and test/fixtures/, which are part of the stated acceptance criteria for this PR/issue. This can cause unintended CI failures on fixture files.

Targeted pattern update
-              node_modules/*|dist/*|build/*|docs/*|nemoclaw/node_modules/*|.venv/*|_deps/*) continue ;;
+              node_modules/*|dist/*|build/*|docs/*|testdata/*|test/fixtures/*|nemoclaw/node_modules/*|.venv/*|_deps/*) continue ;;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
node_modules/*|dist/*|build/*|docs/*|nemoclaw/node_modules/*|.venv/*|_deps/*) continue ;;
node_modules/*|dist/*|build/*|docs/*|testdata/*|test/fixtures/*|nemoclaw/node_modules/*|.venv/*|_deps/*) continue ;;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/pr.yaml at line 180, The exclusion glob list in the CI
workflow string that currently contains
"node_modules/*|dist/*|build/*|docs/*|nemoclaw/node_modules/*|.venv/*|_deps/*"
needs to also exclude test fixture paths: add "testdata/*" and "test/fixtures/*"
to that same pattern so CI ignores those directories; update the matching
pattern (the one containing
node_modules/*|dist/*|build/*|docs/*|nemoclaw/node_modules/*|.venv/*|_deps/*) to
include these two new segments separated by |.

Add a check-spdx-headers job to the PR workflow that runs the existing
scripts/check-spdx-headers.sh (check-only mode, no --fix) against
source files changed in the PR.

The job diffs .ts, .js, .py, .sh, .yaml, and .yml files against the
PR base, excludes generated/vendored paths (node_modules, dist, docs,
.venv), and prints the expected header format on failure so contributors
can copy-paste it.

Fixes NVIDIA#551
@Junior00619
Junior00619 force-pushed the ci/add-spdx-header-check branch from 17a26e1 to ce9db2c Compare March 25, 2026 00:45
@HagegeR

HagegeR commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

The prek hooks should take care of that, and they also run in CI

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

🧹 Nitpick comments (1)
.github/workflows/pr.yaml (1)

191-193: Consider dynamically generating the expected header year.

The year "2026" is hardcoded in this informational message. While this doesn't affect validation (the script's COPYRIGHT_SUBSTR only matches the prefix without the year), it will become stale and could confuse contributors in future years.

Optional: Use dynamic year substitution
       - name: Check SPDX license headers
         if: steps.changed.outputs.skip != 'true'
         run: |
+          year=$(date +%Y)
           echo ""
           echo "Expected header (comment style varies by language):"
-          echo "  SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved."
+          echo "  SPDX-FileCopyrightText: Copyright (c) $year NVIDIA CORPORATION & AFFILIATES. All rights reserved."
           echo "  SPDX-License-Identifier: Apache-2.0"
           echo ""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/pr.yaml around lines 191 - 193, The informational echo
lines hardcode the year "2026", which will become stale; update the workflow to
substitute the current year dynamically (e.g., use the shell date command) when
constructing the expected header message so the echoed lines reflect the current
year; change the two echo statements that print the SPDX-FileCopyrightText and
SPDX-License-Identifier to use a dynamic year variable (computed via date +%Y)
instead of the literal "2026" and keep the existing COPYRIGHT_SUBSTR behavior
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.github/workflows/pr.yaml:
- Around line 191-193: The informational echo lines hardcode the year "2026",
which will become stale; update the workflow to substitute the current year
dynamically (e.g., use the shell date command) when constructing the expected
header message so the echoed lines reflect the current year; change the two echo
statements that print the SPDX-FileCopyrightText and SPDX-License-Identifier to
use a dynamic year variable (computed via date +%Y) instead of the literal
"2026" and keep the existing COPYRIGHT_SUBSTR behavior unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c268ad1c-d22f-4269-b504-aff44cc89dbe

📥 Commits

Reviewing files that changed from the base of the PR and between 17a26e1 and ce9db2c.

📒 Files selected for processing (1)
  • .github/workflows/pr.yaml

@Junior00619

Copy link
Copy Markdown
Contributor Author

The prek hooks should take care of that, and they also run in CI

Good call — I didn't realize the lint job's prek run --all-files --stage pre-push already covers the SPDX hook. That makes this redundant. Happy to close the PR.

@wscurran wscurran added the chore Build, CI, dependency, or tooling maintenance label Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

chore Build, CI, dependency, or tooling maintenance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ci: add SPDX license header check for source files

3 participants