From 659b266ca9abd13b3579698398c93ccd9e45056f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Aug 2026 22:40:44 +0000 Subject: [PATCH 1/9] Initial plan From 306c5f9cc0111dfcc5c86c1472f5c953a8d7bdd9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Aug 2026 22:42:36 +0000 Subject: [PATCH 2/9] Add structured action results output Co-authored-by: felickz <1760475+felickz@users.noreply.github.com> --- README.md | 30 ++++++++++++++++++++++++++++++ action.yml | 24 +++++++++++++++++++++++- ghascompliance/__main__.py | 12 +++++++++++- ghascompliance/output.py | 21 +++++++++++++++++++++ tests/test_output.py | 24 ++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 ghascompliance/output.py create mode 100644 tests/test_output.py diff --git a/README.md b/README.md index d7baaa6..99bd533 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,36 @@ Here is how you can quickly setup policy-as-code. uses: advanced-security/policy-as-code@v2.11.1 ``` +#### Structured results + +The action exposes its results as JSON for downstream steps through the `results` +output. It also writes the same JSON to `.compliance/results.json` by default. +Set the `output` input to use a different file location. + +```yaml +- name: Advance Security Policy as Code + id: policy + uses: advanced-security/policy-as-code@v2.11.1 + +- name: Read total violations + run: echo '${{ fromJSON(steps.policy.outputs.results).total_violations }}' +``` + +The JSON schema is: + +```json +{ + "schema_version": 1, + "total_violations": 2, + "checks": { + "code_scanning": 1, + "dependabot": 1 + } +} +``` + +`checks` includes each enabled check and its violation count. + > [!WARNING] > The GitHub Action does not install Python on the runner. Please checkout at [the `actions/setup-python` Action][python-setup] diff --git a/action.yml b/action.yml index cfa137e..b8c8830 100644 --- a/action.yml +++ b/action.yml @@ -42,10 +42,20 @@ inputs: argvs: description: "Additional Arguments" + output: + description: Path where structured JSON results are written + default: .compliance/results.json + +outputs: + results: + description: Structured JSON results, with schema_version, total_violations, and per-check counts + value: ${{ steps.results.outputs.results }} + runs: using: "composite" steps: - - shell: bash + - id: policy + shell: bash run: | echo "Running Policy as Code..." export PYTHONPATH=${{ github.action_path }}:${{ github.action_path }}/vendor @@ -60,4 +70,16 @@ runs: --github-policy-path "${{ inputs.policy-path }}" \ --github-policy-branch "${{ inputs.policy-branch }}" \ --retry-count "${{ inputs.retries }}" \ + --output "${{ inputs.output }}" \ ${{ inputs.argvs }} + - id: results + if: always() + shell: bash + run: | + if [ -f "${{ inputs.output }}" ]; then + { + echo 'results<> "$GITHUB_OUTPUT" + fi diff --git a/ghascompliance/__main__.py b/ghascompliance/__main__.py index 83dc8c2..c09fd81 100644 --- a/ghascompliance/__main__.py +++ b/ghascompliance/__main__.py @@ -9,6 +9,7 @@ from ghascompliance.octokit import Octokit, PullRequest, Summary from ghascompliance.policy import Policy from ghascompliance.checks import * +from ghascompliance.output import write_results # https://docs.github.com/en/actions/reference/environment-variables#default-environment-variables GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN") @@ -65,6 +66,11 @@ thresholds.add_argument("--severity", default="Error") thresholds.add_argument("--list-severities", action="store_true") thresholds.add_argument("--count", type=int, default=-1) +parser.add_argument( + "--output", + default=os.path.join(".compliance", "results.json"), + help="Path to write structured policy check results as JSON", +) if __name__ == "__main__": @@ -197,6 +203,7 @@ ) errors = 0 + check_results = {} checks = [ ("code_scanning", checks.checkCodeScanning), @@ -209,7 +216,9 @@ for check in checks: try: if not getattr(arguments, f"disable_{check[0]}"): - errors += check[1]() + violations = check[1]() + errors += violations + check_results[check[0]] = violations except GHASToolkitAuthenticationError as err: Octokit.error("Authentication Error") @@ -237,6 +246,7 @@ raise err Octokit.endGroup() + write_results(arguments.output, errors, check_results) Octokit.createGroup("Summary") diff --git a/ghascompliance/output.py b/ghascompliance/output.py new file mode 100644 index 0000000..017f539 --- /dev/null +++ b/ghascompliance/output.py @@ -0,0 +1,21 @@ +import json +import os +from typing import Dict + + +def write_results(path: str, total_violations: int, checks: Dict[str, int]) -> None: + """Write policy check results to a JSON file.""" + output_directory = os.path.dirname(path) + if output_directory: + os.makedirs(output_directory, exist_ok=True) + + with open(path, "w", encoding="utf-8") as handle: + json.dump( + { + "schema_version": 1, + "total_violations": total_violations, + "checks": checks, + }, + handle, + indent=2, + ) diff --git a/tests/test_output.py b/tests/test_output.py new file mode 100644 index 0000000..9b582fe --- /dev/null +++ b/tests/test_output.py @@ -0,0 +1,24 @@ +import json +import os +import tempfile +import unittest + +from ghascompliance.output import write_results + + +class TestOutput(unittest.TestCase): + def testWriteResultsCreatesStructuredJson(self): + with tempfile.TemporaryDirectory() as directory: + path = os.path.join(directory, "results", "output.json") + + write_results(path, 2, {"code_scanning": 1, "dependabot": 1}) + + with open(path, encoding="utf-8") as handle: + self.assertEqual( + json.load(handle), + { + "schema_version": 1, + "total_violations": 2, + "checks": {"code_scanning": 1, "dependabot": 1}, + }, + ) From 4434b1bda831923fa881d125cc217d866aba7133 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Aug 2026 22:43:36 +0000 Subject: [PATCH 3/9] Harden structured output handling Co-authored-by: felickz <1760475+felickz@users.noreply.github.com> --- action.yml | 5 +++-- tests/test_output.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/action.yml b/action.yml index b8c8830..22f726c 100644 --- a/action.yml +++ b/action.yml @@ -77,9 +77,10 @@ runs: shell: bash run: | if [ -f "${{ inputs.output }}" ]; then + delimiter="$(openssl rand -hex 8)" { - echo 'results<> "$GITHUB_OUTPUT" fi diff --git a/tests/test_output.py b/tests/test_output.py index 9b582fe..6ba2e4c 100644 --- a/tests/test_output.py +++ b/tests/test_output.py @@ -7,7 +7,7 @@ class TestOutput(unittest.TestCase): - def testWriteResultsCreatesStructuredJson(self): + def test_write_results_creates_structured_json(self): with tempfile.TemporaryDirectory() as directory: path = os.path.join(directory, "results", "output.json") From 733fb9cec65c6a453c68302d06f041fc548fd441 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Aug 2026 22:54:30 +0000 Subject: [PATCH 4/9] Address review feedback and fix action output CI failures Co-authored-by: felickz <1760475+felickz@users.noreply.github.com> --- README.md | 22 +++++++++--- action.yml | 14 ++++---- .../authentication-permissions.md | 9 ++--- docs/introduction/cli.md | 1 - docs/policies/basics.md | 1 - docs/samples/typosquatting.md | 2 -- ghascompliance/__main__.py | 30 +++++++++++++--- ghascompliance/output.py | 11 ++++-- tests/test_output.py | 35 +++++++++++++++++-- 9 files changed, 96 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 99bd533..7b97162 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ output. It also writes the same JSON to `.compliance/results.json` by default. Set the `output` input to use a different file location. ```yaml -- name: Advance Security Policy as Code +- name: Advanced Security Policy as Code id: policy uses: advanced-security/policy-as-code@v2.11.1 @@ -88,14 +88,28 @@ The JSON schema is: { "schema_version": 1, "total_violations": 2, + "total_errors": 1, "checks": { - "code_scanning": 1, - "dependabot": 1 + "code_scanning": { + "status": "success", + "violations": 1 + }, + "dependabot": { + "status": "success", + "violations": 1 + }, + "secret_scanning": { + "status": "error", + "violations": 0, + "error": "Authentication Error" + } } } ``` -`checks` includes each enabled check and its violation count. +`checks` includes each enabled check with its `status` (`success` or `error`) and +violation count. Checks that fail with an error also include an `error` message, +are counted in `total_errors`, and are not included in `total_violations`. > [!WARNING] > The GitHub Action does not install Python on the runner. Please checkout at [the `actions/setup-python` Action][python-setup] diff --git a/action.yml b/action.yml index 22f726c..1a604f3 100644 --- a/action.yml +++ b/action.yml @@ -48,7 +48,7 @@ inputs: outputs: results: - description: Structured JSON results, with schema_version, total_violations, and per-check counts + description: Structured JSON results, with schema_version, total_violations, total_errors, and per-check results value: ${{ steps.results.outputs.results }} runs: @@ -75,12 +75,14 @@ runs: - id: results if: always() shell: bash + env: + RESULTS_FILE: ${{ inputs.output }} run: | - if [ -f "${{ inputs.output }}" ]; then - delimiter="$(openssl rand -hex 8)" + if [ -f "$RESULTS_FILE" ]; then + delimiter="$(python3 -c 'import secrets; print(secrets.token_hex(16))')" { - echo "results<<$delimiter" - cat "${{ inputs.output }}" - echo "$delimiter" + printf 'results<<%s\n' "$delimiter" + cat "$RESULTS_FILE" + printf '\n%s\n' "$delimiter" } >> "$GITHUB_OUTPUT" fi diff --git a/docs/introduction/authentication-permissions.md b/docs/introduction/authentication-permissions.md index 590415a..a3e31f7 100644 --- a/docs/introduction/authentication-permissions.md +++ b/docs/introduction/authentication-permissions.md @@ -3,18 +3,15 @@ GHAS Compliance uses primarily the GitHub REST and GraphQL API's to perform specific tasks and actions. This requires authenticating using a GitHub Access Token which can access various services endpoints. - ## Permissions The main use case using GitHub Action uses an [automatic token authentication](https://docs.github.com/en/actions/security-guides/automatic-token-authentication) which might [not have the permissions needed for every policy](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token). - ### Code Scanning [GitHub Code Scanning API](https://docs.github.com/en/rest/reference/code-scanning) requires the ability to read Code Scanning results which can be accessed using Action generated Tokens. - -*Versions: GHES <= 3.0* +*Versions:* GHES <= 3.0 ### Dependencies @@ -22,7 +19,6 @@ GitHub Dependency Graph & Dependabot requires various permissions to access the *Note:* Default Action generated Tokens don't support accessing this API. - ### Secret Scanning Secret Scanning requires a lot of permissions to access the content from the API. @@ -31,7 +27,6 @@ Secret Scanning requires a lot of permissions to access the content from the API Source: [GitHub docs](https://docs.github.com/en/rest/reference/secret-scanning#list-secret-scanning-alerts-by-organization) - *Note:* Default Action generated Tokens don't support accessing this API. -*Versions: GHES <= 3.1* +*Versions:* GHES <= 3.1 diff --git a/docs/introduction/cli.md b/docs/introduction/cli.md index 34c2a39..718a43f 100644 --- a/docs/introduction/cli.md +++ b/docs/introduction/cli.md @@ -17,4 +17,3 @@ pipenv run main --help # ... or pipenv run python -m ghascompliance ``` - diff --git a/docs/policies/basics.md b/docs/policies/basics.md index f77139c..b136adf 100644 --- a/docs/policies/basics.md +++ b/docs/policies/basics.md @@ -50,7 +50,6 @@ There are a number of levels are [specified in the standard and are constant](ht - `note` - `notes` - ### Conditional Policy The conditional policy attributes allow users to write more complex conditional checks. diff --git a/docs/samples/typosquatting.md b/docs/samples/typosquatting.md index 8b14da6..5ea8b86 100644 --- a/docs/samples/typosquatting.md +++ b/docs/samples/typosquatting.md @@ -3,7 +3,6 @@ A big issue that you might want to verify is that an application is using a dependency which is not vulnerable per say but is a know dependency which has been miss typed to get developers using it. These dependencies might contain crypto miners all the way to code exfiltrating malware. - ## Enabling built in list Enabling this check only requires importing the built in list of known dependencies: @@ -18,4 +17,3 @@ dependencies: # Import text list of Typo-squatting dependencies. names: ghascompliance/defaults/typosquatting.txt ``` - diff --git a/ghascompliance/__main__.py b/ghascompliance/__main__.py index c09fd81..bcd3217 100644 --- a/ghascompliance/__main__.py +++ b/ghascompliance/__main__.py @@ -203,9 +203,11 @@ ) errors = 0 + total_violations = 0 + total_errors = 0 check_results = {} - checks = [ + checks_to_run = [ ("code_scanning", checks.checkCodeScanning), ("dependabot", checks.checkDependabot), ("dependencies", checks.checkDependencies), @@ -213,18 +215,28 @@ ("secret_scanning", checks.checkSecretScanning), ] - for check in checks: + for check in checks_to_run: try: if not getattr(arguments, f"disable_{check[0]}"): violations = check[1]() errors += violations - check_results[check[0]] = violations + total_violations += violations + check_results[check[0]] = { + "status": "success", + "violations": violations, + } except GHASToolkitAuthenticationError as err: Octokit.error("Authentication Error") Octokit.error(str(err)) errors += 1 + total_errors += 1 + check_results[check[0]] = { + "status": "error", + "violations": 0, + "error": str(err), + } # Add to summary Summary.addLine(f"{Summary.__ICONS__['cross']} :: Authentication Error") Summary.addLine(Summary.formatItalics(str(err))) @@ -234,6 +246,12 @@ Octokit.error(str(err)) errors += 1 # add to error count + total_errors += 1 + check_results[check[0]] = { + "status": "error", + "violations": 0, + "error": str(err), + } # Add to summary Summary.addHeader(f"{Summary.__ICONS__['cross']} :: Error Encountered", 2) @@ -246,7 +264,11 @@ raise err Octokit.endGroup() - write_results(arguments.output, errors, check_results) + + try: + write_results(arguments.output, total_violations, total_errors, check_results) + except OSError as err: + Octokit.warning(f"Unable to write results file :: {err}") Octokit.createGroup("Summary") diff --git a/ghascompliance/output.py b/ghascompliance/output.py index 017f539..0d5a8e2 100644 --- a/ghascompliance/output.py +++ b/ghascompliance/output.py @@ -1,9 +1,14 @@ import json import os -from typing import Dict +from typing import Any, Dict -def write_results(path: str, total_violations: int, checks: Dict[str, int]) -> None: +def write_results( + path: str, + total_violations: int, + total_errors: int, + checks: Dict[str, Dict[str, Any]], +) -> None: """Write policy check results to a JSON file.""" output_directory = os.path.dirname(path) if output_directory: @@ -14,8 +19,10 @@ def write_results(path: str, total_violations: int, checks: Dict[str, int]) -> N { "schema_version": 1, "total_violations": total_violations, + "total_errors": total_errors, "checks": checks, }, handle, indent=2, ) + handle.write("\n") diff --git a/tests/test_output.py b/tests/test_output.py index 6ba2e4c..a9b8107 100644 --- a/tests/test_output.py +++ b/tests/test_output.py @@ -11,7 +11,20 @@ def test_write_results_creates_structured_json(self): with tempfile.TemporaryDirectory() as directory: path = os.path.join(directory, "results", "output.json") - write_results(path, 2, {"code_scanning": 1, "dependabot": 1}) + write_results( + path, + 2, + 1, + { + "code_scanning": {"status": "success", "violations": 1}, + "dependabot": {"status": "success", "violations": 1}, + "secret_scanning": { + "status": "error", + "violations": 0, + "error": "Authentication Error", + }, + }, + ) with open(path, encoding="utf-8") as handle: self.assertEqual( @@ -19,6 +32,24 @@ def test_write_results_creates_structured_json(self): { "schema_version": 1, "total_violations": 2, - "checks": {"code_scanning": 1, "dependabot": 1}, + "total_errors": 1, + "checks": { + "code_scanning": {"status": "success", "violations": 1}, + "dependabot": {"status": "success", "violations": 1}, + "secret_scanning": { + "status": "error", + "violations": 0, + "error": "Authentication Error", + }, + }, }, ) + + def test_write_results_ends_with_newline(self): + with tempfile.TemporaryDirectory() as directory: + path = os.path.join(directory, "output.json") + + write_results(path, 0, 0, {}) + + with open(path, encoding="utf-8") as handle: + self.assertTrue(handle.read().endswith("\n")) From 83eb75b1df738d6fe6f23ece0e8702b0533a639e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Aug 2026 22:56:19 +0000 Subject: [PATCH 5/9] Document write_results parameters Co-authored-by: felickz <1760475+felickz@users.noreply.github.com> --- ghascompliance/output.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ghascompliance/output.py b/ghascompliance/output.py index 0d5a8e2..63b18f4 100644 --- a/ghascompliance/output.py +++ b/ghascompliance/output.py @@ -9,7 +9,17 @@ def write_results( total_errors: int, checks: Dict[str, Dict[str, Any]], ) -> None: - """Write policy check results to a JSON file.""" + """Write policy check results to a JSON file. + + Args: + path: File path to write the JSON results to. Missing parent + directories are created. + total_violations: Total number of policy violations found by the + checks that ran successfully. + total_errors: Number of checks that failed with an error. + checks: Per-check results, keyed by check name, each containing the + check `status`, its `violations` count and, on failure, an `error`. + """ output_directory = os.path.dirname(path) if output_directory: os.makedirs(output_directory, exist_ok=True) From ef3ed04368ff4237d9df37db6264150c4533f01c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 19 Aug 2026 04:55:50 +0000 Subject: [PATCH 6/9] Address remaining PR review suggestions Co-authored-by: felickz <1760475+felickz@users.noreply.github.com> --- action.yml | 4 ++-- ghascompliance/__main__.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/action.yml b/action.yml index 1a604f3..fc6100a 100644 --- a/action.yml +++ b/action.yml @@ -79,10 +79,10 @@ runs: RESULTS_FILE: ${{ inputs.output }} run: | if [ -f "$RESULTS_FILE" ]; then - delimiter="$(python3 -c 'import secrets; print(secrets.token_hex(16))')" + delimiter="$(python -c 'import secrets; print(secrets.token_hex(16))' 2>/dev/null || python3 -c 'import secrets; print(secrets.token_hex(16))')" { printf 'results<<%s\n' "$delimiter" cat "$RESULTS_FILE" - printf '\n%s\n' "$delimiter" + printf '%s\n' "$delimiter" } >> "$GITHUB_OUTPUT" fi diff --git a/ghascompliance/__main__.py b/ghascompliance/__main__.py index bcd3217..19b01b5 100644 --- a/ghascompliance/__main__.py +++ b/ghascompliance/__main__.py @@ -215,13 +215,13 @@ ("secret_scanning", checks.checkSecretScanning), ] - for check in checks_to_run: + for check_name, check_fn in checks_to_run: try: - if not getattr(arguments, f"disable_{check[0]}"): - violations = check[1]() + if not getattr(arguments, f"disable_{check_name}"): + violations = check_fn() errors += violations total_violations += violations - check_results[check[0]] = { + check_results[check_name] = { "status": "success", "violations": violations, } @@ -232,7 +232,7 @@ errors += 1 total_errors += 1 - check_results[check[0]] = { + check_results[check_name] = { "status": "error", "violations": 0, "error": str(err), @@ -247,7 +247,7 @@ errors += 1 # add to error count total_errors += 1 - check_results[check[0]] = { + check_results[check_name] = { "status": "error", "violations": 0, "error": str(err), From 502c62294c62a7a099358a03cc969d92b5f60624 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 19 Aug 2026 05:23:12 +0000 Subject: [PATCH 7/9] Provide fallback action results output Co-authored-by: felickz <1760475+felickz@users.noreply.github.com> --- action.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/action.yml b/action.yml index fc6100a..3dcb0ef 100644 --- a/action.yml +++ b/action.yml @@ -78,11 +78,13 @@ runs: env: RESULTS_FILE: ${{ inputs.output }} run: | - if [ -f "$RESULTS_FILE" ]; then - delimiter="$(python -c 'import secrets; print(secrets.token_hex(16))' 2>/dev/null || python3 -c 'import secrets; print(secrets.token_hex(16))')" - { - printf 'results<<%s\n' "$delimiter" + delimiter="$(python -c 'import secrets; print(secrets.token_hex(16))' 2>/dev/null || python3 -c 'import secrets; print(secrets.token_hex(16))')" + { + printf 'results<<%s\n' "$delimiter" + if [ -f "$RESULTS_FILE" ]; then cat "$RESULTS_FILE" - printf '%s\n' "$delimiter" - } >> "$GITHUB_OUTPUT" - fi + else + printf '%s\n' '{"schema_version":1,"total_violations":0,"total_errors":0,"checks":{}}' + fi + printf '%s\n' "$delimiter" + } >> "$GITHUB_OUTPUT" From 33549131679b28b438c1805682b1694c744f796c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 23 Aug 2026 01:01:06 +0000 Subject: [PATCH 8/9] Emit clearly-errored fallback JSON when results file is missing Co-authored-by: felickz <1760475+felickz@users.noreply.github.com> --- action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/action.yml b/action.yml index 3dcb0ef..9e1e015 100644 --- a/action.yml +++ b/action.yml @@ -84,7 +84,7 @@ runs: if [ -f "$RESULTS_FILE" ]; then cat "$RESULTS_FILE" else - printf '%s\n' '{"schema_version":1,"total_violations":0,"total_errors":0,"checks":{}}' + printf '%s\n' '{"schema_version":1,"total_violations":null,"total_errors":1,"checks":{},"error":"Results file was not found; the run may have failed before writing results"}' fi printf '%s\n' "$delimiter" } >> "$GITHUB_OUTPUT" From 0f14c45e3769fc4d214090bb29098f3f5e8b7b98 Mon Sep 17 00:00:00 2001 From: Chad Bentz <1760475+felickz@users.noreply.github.com> Date: Sat, 22 Aug 2026 21:19:32 -0400 Subject: [PATCH 9/9] Address second review: null total_errors in fallback, document fallback schema - action.yml: set total_errors to null (not 1) in the fallback JSON emitted when the results file is missing, keeping it consistent with total_violations being null since the real count is unknown. - action.yml: update the results output description to mention the fallback payload contract. - README.md: document the fallback payload schema with an example. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5ba23ba2-e979-47aa-b538-49f686ab6eb9 --- README.md | 15 +++++++++++++++ action.yml | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4bcf68e..3f2e291 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,21 @@ The JSON schema is: violation count. Checks that fail with an error also include an `error` message, are counted in `total_errors`, and are not included in `total_violations`. +If the results file is missing, for example because the run failed before writing +it, the action emits a fallback payload instead. `total_violations` and +`total_errors` are `null` since the real counts are unknown, `checks` is empty, +and a top-level `error` describes the failure: + +```json +{ + "schema_version": 1, + "total_violations": null, + "total_errors": null, + "checks": {}, + "error": "Results file was not found; the run may have failed before writing results" +} +``` + > [!WARNING] > The GitHub Action does not install Python on the runner. Please checkout at [the `actions/setup-python` Action][python-setup] diff --git a/action.yml b/action.yml index 9e1e015..283ce10 100644 --- a/action.yml +++ b/action.yml @@ -48,7 +48,7 @@ inputs: outputs: results: - description: Structured JSON results, with schema_version, total_violations, total_errors, and per-check results + description: Structured JSON results, with schema_version, total_violations, total_errors, and per-check results. If the results file is missing (for example, the run failed before writing it), a fallback payload is emitted instead with total_violations and total_errors set to null and a top-level error message describing the failure. value: ${{ steps.results.outputs.results }} runs: @@ -84,7 +84,7 @@ runs: if [ -f "$RESULTS_FILE" ]; then cat "$RESULTS_FILE" else - printf '%s\n' '{"schema_version":1,"total_violations":null,"total_errors":1,"checks":{},"error":"Results file was not found; the run may have failed before writing results"}' + printf '%s\n' '{"schema_version":1,"total_violations":null,"total_errors":null,"checks":{},"error":"Results file was not found; the run may have failed before writing results"}' fi printf '%s\n' "$delimiter" } >> "$GITHUB_OUTPUT"