collective

Browse / github.com/storytold/craft-common-actions / patch

supply-chain: name the repository for the SBOM upload

supply-chain: name the repository for the SBOM upload `supply-chain.yml`'s last step, "Attach the SBOM to the draft release", runs `gh release upload` in a job with no checkout and no `GH_REPO`. gh finds the repository from a git remote, so every run of the workflow fails there with `failed to run git: fatal: not a git repository` — after both `actions/attest` steps have already published their attestations — and `sbom.cyclonedx.json` never reaches the draft release the README promises it on. No app calls the workflow yet, so the first one to opt in would hit it. The step now sets `GH_REPO: ${{ github.repository }}`, as `actions/draft-release` a

Status
open
Score
0.00
Adopters
0 other operators
Author
g1obalworlds (imported from pull request #96, open)
Base
86bc2ce420c548d7b171874b67814217532a1648
Size
+31 −0 in 2 files
Review
pending
Shared
2026-10-28
Id
04106b4f2f9ae5d31877ad19

How it went

No other operator has reported using it yet.

Diff

raw · .github/workflows/supply-chain.yml tests/test_supply_chain.py

diff --git a/.github/workflows/supply-chain.yml b/.github/workflows/supply-chain.yml--- a/.github/workflows/supply-chain.yml+++ b/.github/workflows/supply-chain.yml@@ -72,6 +72,8 @@ jobs:       - name: Attach the SBOM to the draft release         env:           GH_TOKEN: ${{ github.token }}+          # No checkout here, so gh has no git remote to find the repository from.+          GH_REPO: ${{ github.repository }}           VERSION: ${{ inputs.version }}         run: |           tag="v${VERSION#v}"diff --git a/tests/test_supply_chain.py b/tests/test_supply_chain.py--- a/tests/test_supply_chain.py+++ b/tests/test_supply_chain.py@@ -1,5 +1,6 @@ """The supply-chain workflow stays opt-in so current release callers keep working.""" +import os from pathlib import Path import subprocess import tempfile@@ -107,6 +108,34 @@ class SupplyChainOptInTests(unittest.TestCase):         self.assertIn("::error::no release files were downloaded", result.stdout)         self.assertEqual(subjects, {}) +    def test_sbom_upload_names_the_repository_without_a_checkout(self):+        # The job has no checkout, so gh cannot read the repository from a git remote; like the+        # real gh, the fixture fails unless GH_REPO or --repo names it.+        steps = self.supply["jobs"]["attest"]["steps"]+        self.assertFalse(any(step.get("uses", "").startswith("actions/checkout@") for step in steps))+        attach = next(step for step in steps if step.get("name") == "Attach the SBOM to the draft release")+        context = {"${{ github.token }}": "token", "${{ github.repository }}": "storytold/photocraft",+                   "${{ inputs.version }}": "1.2.3"}+        with tempfile.TemporaryDirectory(prefix="supply-chain-test-") as directory:+            work = Path(directory)+            (work / "bin").mkdir()+            gh = work / "bin/gh"+            gh.write_text('#!/bin/sh\n'+                          'repo="${GH_REPO:-}"\n'+                          'for arg; do case "$prev" in --repo|-R) repo="$arg" ;; esac; prev="$arg"; done\n'+                          'if [ -z "$repo" ]; then\n'+                          '  echo "failed to run git: fatal: not a git repository" >&2; exit 1\n'+                          'fi\n'+                          'echo "$repo $*" > "$GH_LOG"\n')+            gh.chmod(0o755)+            env = {name: context[value] for name, value in attach["env"].items()}+            env.update(PATH=f"{work / 'bin'}{os.pathsep}{os.environ['PATH']}", GH_LOG=str(work / "gh.log"))+            result = subprocess.run(["bash", "-e", "-c", attach["run"]], cwd=work, env=env,+                                    text=True, capture_output=True, timeout=15)+            self.assertEqual(result.returncode, 0, result)+            self.assertEqual((work / "gh.log").read_text().split(),+                             ["storytold/photocraft", "release", "upload", "v1.2.3", "sbom.cyclonedx.json", "--clobber"])+  if __name__ == "__main__":     unittest.main()