Skip to content

Add Grant license scanning to gh aw compile#47516

Merged
pelikhan merged 7 commits into
mainfrom
copilot/add-grant-container-license-scan-tool
Jul 23, 2026
Merged

Add Grant license scanning to gh aw compile#47516
pelikhan merged 7 commits into
mainfrom
copilot/add-grant-container-license-scan-tool

Conversation

Copilot AI commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

This adds Grant as an opt-in compile-time scanner for container images referenced by compiled workflow manifests. It extends the existing compile scanner model so license-policy checks can run alongside workflow lint and security analysis.

  • CLI and compile plumbing

    • Adds --grant to gh aw compile.
    • Threads the flag through CompileConfig, the compile pipeline, and the MCP compile tool schema/dispatch path.
  • Container image license scanning

    • Introduces a Grant-backed scanner for images extracted from gh-aw-manifest headers in generated .lock.yml files.
    • Reuses the same manifest-driven image collection flow as the existing container-image scanner path.
    • Reports denied-license findings as compile errors, and honors strict-mode failure behavior.
  • Docker tool preparation

    • Registers the Grant container image in the shared Docker image preparation path used by compile-time scanners.
    • Ensures MCP compile requests handle Grant the same way they handle other Docker-backed scanners.
  • Policy source

    • Uses the repository-root .grant.yaml as the license policy input for Grant runs, keeping compile-time behavior aligned with the repository’s existing license rules.
  • Docs and surface area

    • Updates CLI and MCP docs to include --grant anywhere compile scanner options are described.
gh aw compile --grant
gh aw compile --strict --grant

Generated by 👨‍🍳 PR Sous Chef · gpt54 15 AIC · ⌖ 8.9 AIC · ⊞ 7K ·
Comment /souschef to run again

Copilot AI and others added 2 commits July 23, 2026 07:14
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title Add grant license scanning to the compile command Add Grant license scanning to gh aw compile Jul 23, 2026
Copilot AI requested a review from pelikhan July 23, 2026 07:18
@pelikhan
pelikhan marked this pull request as ready for review July 23, 2026 07:20
Copilot AI review requested due to automatic review settings July 23, 2026 07:20

Copilot AI 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.

Pull request overview

Adds opt-in Grant license-policy scanning for container images during workflow compilation.

Changes:

  • Adds --grant to CLI, compile, and MCP paths.
  • Implements Grant execution, policy loading, result reporting, and Docker preparation.
  • Adds tests and documentation for the new scanner.
Show a summary per file
File Description
cmd/gh-aw/main.go Registers and configures --grant.
cmd/gh-aw/compile_flags_test.go Tests Grant flag registration.
pkg/cli/compile_config.go Adds Grant configuration.
pkg/cli/compile_pipeline.go Integrates Grant into batch compilation.
pkg/cli/compile_external_tools.go Exposes the Grant runner.
pkg/cli/grant.go Implements Grant scanning and reporting.
pkg/cli/grant_test.go Tests Grant helpers.
pkg/cli/docker_images.go Registers the Grant Docker image.
pkg/cli/docker_images_test.go Updates Docker preparation tests.
pkg/cli/mcp_tools_readonly.go Adds Grant to MCP compile requests.
pkg/cli/mcp_server_defaults_test.go Updates the compile schema fixture.
docs/src/content/docs/setup/cli.md Documents the CLI flag.
docs/src/content/docs/reference/gh-aw-as-mcp-server.md Documents MCP support.
docs/src/content/docs/reference/compilation-process.md Adds Grant to compilation examples.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (2)

pkg/cli/compile_pipeline.go:466

  • The all-workflows path also places Grant inside the Grype conditional. As a result, gh aw compile --grant without explicit workflow arguments silently skips license scanning unless --grype is enabled.
		// Run grant license scanner on container images referenced in the compiled lock files.
		if config.Grant && !config.NoEmit && len(lockFilesForGrant) > 0 {

pkg/cli/compile_pipeline.go:472

  • The directory compilation path has the same strict JSON failure: a Grant policy violation returns before outputResults, causing MCP compile-all requests to receive a protocol error with no JSON result. Preserve the scanner findings in validationResults and emit the JSON response before returning the strict-mode failure.
			if err := RunGrantOnLockFiles(lockFilesForGrant, config.Verbose && !config.JSONOutput, config.Strict); err != nil {
				if config.Strict {
					return workflowDataList, err
  • Files reviewed: 14/14 changed files
  • Comments generated: 2
  • Review effort level: Medium

Comment on lines +228 to 240

// Run grant license scanner on container images referenced in the compiled lock files.
if config.Grant && !config.NoEmit && len(lockFilesForGrant) > 0 {
if err := ctx.Err(); err != nil {
return workflowDataList, err
}
if err := RunGrantOnLockFiles(lockFilesForGrant, config.Verbose && !config.JSONOutput, config.Strict); err != nil {
if config.Strict {
return workflowDataList, err
}
}
}
}
Comment thread pkg/cli/compile_pipeline.go Outdated
Comment on lines +234 to +236
if err := RunGrantOnLockFiles(lockFilesForGrant, config.Verbose && !config.JSONOutput, config.Strict); err != nil {
if config.Strict {
return workflowDataList, err
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

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

REQUEST_CHANGES — two correctness issues in grant.go and a misclassified test need fixes before merge.

Blocking findings summary

1. runErr silently discarded (grant.go:559)

When grant exits non-zero but emits parseable JSON with at least one non-error target, grantRunOnImage discards the exit code and returns success. A transient Docker failure or OOM that produces partial output would be silently swallowed.

2. Total scan failure returns nil in non-strict mode (grant.go:498)

If every image scan fails (all end up in scanErrors), non-strict mode prints a warning and returns nil. The compile pipeline sees a clean result. A 100% failure rate should still return an error regardless of strict mode.

3. TestGrantPolicyFile misclassified as unit test (grant_test.go:716)

Tagged !integration but calls grantPolicyFile() which requires .grant.yaml to exist on disk. This will cause flaky failures in forks and sandboxes. Should be //go:build integration or refactored to use a temp file.

🔎 Code quality review by PR Code Quality Reviewer · sonnet46 39.9 AIC · ⌖ 4.95 AIC · ⊞ 5.7K
Comment /review to run again

Comments that could not be inline-anchored

pkg/cli/grant.go:559

runErr is silently discarded when JSON output is parseable: if grant exits non-zero (e.g. transient Docker OOM or unexpected error) but still emits partial JSON with at least one non-error target, grantRunOnImage returns the partial output as success. The caller treats the image as cleanly scanned, hiding the failure.

<details>
<summary>💡 Suggested fix</summary>

After parsing JSON and walking targets, check whether runErr != nil and no target explains the exit:

runErr := cmd</details>

<details><summary>pkg/cli/grant.go:498</summary>

**Total scan failure returns `nil` (success) in non-strict mode**: when every image scan fails and `scanErrors` is non-empty but `strict` is false, the function prints a warning and returns `nil`. The caller in `compile_pipeline.go` has no way to distinguish &quot;scanned cleanly&quot; from &quot;every scan failed&quot; — it always sees success.

&lt;details&gt;
&lt;summary&gt;💡 Suggested fix&lt;/summary&gt;

Return a sentinel or non-nil error even in non-strict mode when *all* images failed (i.e., `len(scanErrors) == len(images)`</details>

<details><summary>pkg/cli/grant_test.go:716</summary>

**`TestGrantPolicyFile` is misclassified as a unit test**: it has the `!integration` build tag but makes a live filesystem call to `grantPolicyFile()`, which requires `.grant.yaml` to exist at the repository root. This will hard-fail in any fork, CI environment, or Docker-based sandbox that doesn&#39;t have that file. It should use `//go:build integration` or be refactored to not touch the real filesystem.

&lt;details&gt;
&lt;summary&gt;💡 Suggested fix&lt;/summary&gt;

Either:
1. Change the build tag to `integrati…

</details>

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

Skills-Based Review

Applied /diagnosing-bugs, /tdd, and /codebase-design — requesting changes on two blocking bugs and three correctness/reliability issues.

Key Themes

Blocking Issues (already noted in existing comments)

  • Grant nested inside Grype block (compile_pipeline.go:230): --grant only runs when --grype is also supplied — the block must be closed and placed at the same level as Grype.
  • Strict-mode early return before outputResults (compile_pipeline.go:236): MCP callers in strict mode receive empty stdout JSON when a license violation is found.

New Issues

  • /tmp/gh-aw-grant-policy.yaml race (grant.go:148): concurrent compile runs on the same host share a single container path — add a PID suffix.
  • grantDisplayFindings ignores JSONOutput mode (grant.go:246): stderr findings pollute MCP responses; needs an io.Writer parameter gated on JSON mode.
  • grantPolicyFile() hard-errors with no guidance (grant.go:72): users in repos without .grant.yaml get an opaque error; improve the message with a hint.
  • TestGrantPolicyFile is environment-coupled (grant_test.go:67): fails in forks/CI without .grant.yaml; inject the root path for testability.

Positive Highlights

  • Clean reuse of collectContainerImagesFromLockFiles — no duplication with the Grype path.
  • Thorough grantRunOnImage error handling (exit code, empty targets, evaluation errors).
  • Good use of runBatchLockFileTool for consistent batch semantics.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 45.5 AIC · ⌖ 4.94 AIC · ⊞ 6.7K
Comment /matt to run again

Comments that could not be inline-anchored

pkg/cli/docker_images.go:225

[/codebase-design] GrantImage uses :latest — confirm Dependabot tracks anchore/grant Docker images, otherwise scan results may silently drift.

<details>
<summary>Suggestion</summary>

Verify .github/dependabot.yml includes anchore/grant. If not, pin to a semver tag to match other scanner images when they get pinned.

</details>

@copilot please address this.

pkg/cli/grant.go:72

[/diagnosing-bugs] grantPolicyFile() returns a hard error when .grant.yaml is absent, so gh aw compile --grant always fails in repos without a policy file — with no actionable guidance.

<details>
<summary>Suggestion</summary>

Improve the error message to include a link to Grant policy docs or a hint to create the file:

return &quot;&quot;, fmt.Errorf(&quot;grant requires %s at the repository root (see https://github.com/anchore/grant#policy): %w&quot;, grantPolicyFilename, err)

</details>

pkg/cli/grant.go:148

[/codebase-design] The hardcoded container path /tmp/gh-aw-grant-policy.yaml is a race if two gh aw compile --grant runs execute concurrently on the same host (e.g. parallel matrix CI jobs).

<details>
<summary>Suggestion</summary>

Add a PID or random suffix to the in-container path:

containerPolicyPath := fmt.Sprintf(&quot;/tmp/gh-aw-grant-policy-%d.yaml&quot;, os.Getpid())

</details>

@copilot please address this.

pkg/cli/grant.go:246

[/tdd] grantDisplayFindings writes directly to os.Stderr even when config.JSONOutput is true (MCP mode). In JSON mode, callers expect clean stdout — this stderr output will appear as noise in MCP responses.

<details>
<summary>Suggestion</summary>

Thread an io.Writer through grantDisplayFindings and conditionally suppress or redirect findings in JSON mode, mirroring the config.Verbose &amp;&amp; !config.JSONOutput guard used on line 78.

</details>

@copilot please address this.

pkg/cli/grant_test.go:67

[/tdd] TestGrantPolicyFile is environment-coupled: it passes only when .grant.yaml exists at the git root. This will fail in forks or CI environments where the file is absent.

<details>
<summary>Suggestion</summary>

Refactor grantPolicyFile to accept a root directory param, then test with t.TempDir() for both missing-file and present-file scenarios. The current test can remain as an integration test gated on the real repository.

</details>

@copilot please address this.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (409 new lines in pkg/ and cmd/) but does not have a linked Architecture Decision Record (ADR).

Draft ADR committed: docs/adr/47516-add-grant-container-license-scan.md — review and complete it before merging.

This PR cannot merge until an ADR is linked in the PR body.

What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI could not infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

ADR: ADR-47516: Add Grant Container Image License Scanning to Compile Pipeline

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

Why ADRs Matter

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 47516-add-grant-container-license-scan.md for PR #47516).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · sonnet46 52.3 AIC · ⌖ 10.3 AIC · ⊞ 8.5K ·
Comment /review to run again

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

Review: Add Grant license scanning to gh aw compile

Good implementation overall — the architecture mirrors Grype cleanly and the test coverage is reasonable. Two blocking issues found.

Blocking

  1. Grant block nested inside Grype guard (compile_pipeline.go lines 230 and 466 — both compileSpecificFiles and compileAllFilesInDirectory): --grant only runs when --grype is also supplied. Both functions need the Grant block moved out as an independent top-level if config.Grant block.

  2. Hard failure when .grant.yaml is absent (grant.go:138): grantPolicyFile() returns an opaque error for any repo that does not already have .grant.yaml. This makes --grant immediately unusable without prior setup. Either skip scanning gracefully with a helpful message, or gate the error on os.IsNotExist and print a setup hint.

Non-blocking (noted by earlier review)

  • Strict-mode early return at line 236 skips outputResults in JSON mode.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 66.1 AIC · ⌖ 10 AIC · ⊞ 5K

Comment thread pkg/cli/compile_pipeline.go Outdated
}

// Run grant license scanner on container images referenced in the compiled lock files.
if config.Grant && !config.NoEmit && len(lockFilesForGrant) > 0 {

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.

Same nesting bug as in compileSpecificFiles: the Grant block (lines 466–475) is inside the if config.Grype block, so --grant without --grype is silently skipped in compileAllFilesInDirectory. Move this block out as a top-level if config.Grant block, parallel to the Grype block at line 455.

@copilot please address this.

Comment thread pkg/cli/grant.go Outdated
policyFile := filepath.Join(repoRoot, grantPolicyFilename)
info, err := os.Stat(policyFile)
if err != nil {
return "", fmt.Errorf("grant requires %s at the repository root: %w", grantPolicyFilename, err)

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.

grantPolicyFile() returns a hard error when .grant.yaml is absent from the repo root. This turns --grant into a compile-breaking flag for every repository that hasn't created that file, even if the user just wants to try the feature. Consider returning a nil policy path (skip scanning gracefully) or at minimum printing a friendly setup hint instead of an opaque os.Stat error.

@copilot please address this.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 80/100 — Excellent

Analyzed 5 test(s): 4 design, 1 implementation, 0 violation(s).

📊 Metrics (5 tests)
Metric Value
Analyzed 5 (Go: 5, JS: 0)
✅ Design 4 (80%)
⚠️ Implementation 1 (20%)
Edge/error coverage 3 (60%)
Duplicate clusters 0
Inflation No
🚨 Violations 0
Test File Classification Issues
TestGrantDisplayFindings_NilOutput pkg/cli/grant_test.go:10 behavioral_contract None — nil guard edge case
TestGrantDisplayFindings_WithDeniedPackages pkg/cli/grant_test.go:21 behavioral_contract None — mixed deny/allow edge coverage
TestRunGrantOnLockFiles_NoLockFiles pkg/cli/grant_test.go:63 behavioral_contract None — empty-input edge case
TestGrantPolicyFile pkg/cli/grant_test.go:70 implementation_detail Happy-path only; checks file basename only
TestCompileCommandShortFlags (grant additions) cmd/gh-aw/compile_flags_test.go:32 behavioral_contract None — verifies CLI flag contract
⚠️ Flagged Tests (1)

TestGrantPolicyFile (pkg/cli/grant_test.go:70) — implementation_detail. Verifies only the basename of the embedded policy file, not that it is syntactically valid or that the grant tool can actually load it. Low risk but worth noting.

Note: docker_images_test.go (+15/-11) and mcp_server_defaults_test.go (+1) changes were signature/parameter updates to existing tests (adding the new grant bool parameter) — not new test logic; excluded from the new-tests count.

Verdict

Passed. 20% implementation tests (threshold: 30%). No guideline violations. Build tags present on all new test files, no forbidden mocks, no test inflation (75 test lines vs 259 production lines in grant.go, ratio ≈ 0.29:1).

🧪 Test quality analysis by Test Quality Sentinel · sonnet46 80.3 AIC · ⌖ 7.79 AIC · ⊞ 7K ·
Comment /review to run again

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

✅ Test Quality Sentinel: 80/100. 20% implementation tests (threshold: 30%). No violations.

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@copilot run pr-finisher skill

Done. I ran the PR finisher flow and pushed fixes in b00c91c to address the compile pipeline review issues for --grant.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address these unresolved review comments, and rerun the relevant checks once the branch is up to date:

Generated by 👨‍🍳 PR Sous Chef · gpt54 15 AIC · ⌖ 8.9 AIC · ⊞ 7K ·
Comment /souschef to run again

Copilot AI commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, address these unresolved review comments, and rerun the relevant checks once the branch is up to date:

  • [Run Grant independently of --grype in `compileS...

Addressed in b00c91c (Grant independent of Grype in both compile paths, strict Grant failures preserved in JSON output, and friendlier missing .grant.yaml guidance). Branch is now up to date at 2ec0c0a, and I reran relevant local checks: make fmt, make lint, make test-unit, make test, and make recompile.

Copilot AI requested a review from gh-aw-bot July 23, 2026 08:31
@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot resolve the merge conflicts on this branch.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts on this branch.

Resolved by merging origin/main into this branch and fixing all conflicts in 4a579f0.

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot resolve the merge conflicts on this branch.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts on this branch.

Resolved in b2ebbe9 by merging origin/main and fixing all conflicts on this branch.

@pelikhan
pelikhan merged commit 128d4e9 into main Jul 23, 2026
19 checks passed
@pelikhan
pelikhan deleted the copilot/add-grant-container-license-scan-tool branch July 23, 2026 10:32
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.83.1

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants