Skip to content

DAT-21268: Persist vulnerability scan results to scan-results branch#514

Merged
jandroav merged 7 commits intomainfrom
DAT-21268-persist-scan-results
Mar 19, 2026
Merged

DAT-21268: Persist vulnerability scan results to scan-results branch#514
jandroav merged 7 commits intomainfrom
DAT-21268-persist-scan-results

Conversation

@jandroav
Copy link
Copy Markdown
Contributor

Summary

  • Adds scripts/persist-scan-results.sh to download scan artifacts and commit them to a persistent scan-results branch
  • Adds a persist-results job to the published images scanning workflow that runs after all scans complete
  • Changes contents permission from read to write to allow pushing to the scan-results branch

This enables the Liquibase Security dashboard to read historical scan data via GitHub Contents API, replacing the 7-day artifact retention window.

Branch structure

scan-results/
  manifest.json
  liquibase/liquibase/<tag>/
    trivy-surface.json
    trivy-deep.json
    grype-results.json
    metadata.json
  liquibase/liquibase-secure/<tag>/
    ...

Test plan

  • Trigger workflow manually: gh workflow run "Published Images Vulnerability Scanning"
  • Verify scan-results branch is created with manifest.json and versioned scan data
  • Verify manifest.json lists all scanned images and tags
  • Verify each version directory contains trivy-surface.json, trivy-deep.json, grype-results.json, metadata.json

🤖 Generated with Claude Code

After vulnerability scans complete, a new persist-results job downloads
all scan artifacts and commits them to a scan-results branch. This gives
the Liquibase Security dashboard persistent access to historical scan
data via GitHub Contents API, replacing the 7-day artifact retention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread scripts/persist-scan-results.sh Outdated
Comment thread scripts/persist-scan-results.sh
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds automation to persist published-image vulnerability scan artifacts into a long-lived scan-results branch (for consumption via GitHub Contents API), extending access beyond the default artifact retention period.

Changes:

  • Introduces scripts/persist-scan-results.sh to collect scan artifacts, update a manifest.json, and push results to scan-results.
  • Updates the published-images scanning workflow to add a persist-results job that runs after scans and pushes results to the branch.
  • Adjusts GitHub Actions contents permissions to enable branch pushes.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
scripts/persist-scan-results.sh New script to assemble artifact outputs, write per-tag metadata, maintain a manifest, and commit/push to scan-results.
.github/workflows/trivy-scan-published-images.yml Adds the persistence job and elevates contents permission to support pushing scan results.

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

Comment thread scripts/persist-scan-results.sh Outdated
Comment thread .github/workflows/trivy-scan-published-images.yml
jandroav and others added 3 commits March 10, 2026 10:57
The artifacts directory was passed as a relative path but the script
cd's into a temporary git worktree, breaking the glob. Convert to
absolute path at the start. Also fix grep -c exit code handling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add parentheses around `sort_by(...) | reverse` so that `reverse`
applies to the sorted array inside each image entry, not to the
outer `with_entries` object — which caused "Cannot index object
with number" at runtime.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Data-driven image name parsing instead of hardcoded prefixes
- Proper semver-aware version sorting (non-semver tags appended)
- Least-privilege: contents:write only on persist-results job
- Fix grep -c fragility under set -euo pipefail
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 16, 2026

Claude finished @jandroav's task in 1m 4s —— View job


Code Review: DAT-21268 - Persist vulnerability scan results

  • Read changed files
  • Assess what was fixed vs. what remains
  • Post review findings

All previous issues resolved ✅

Hardcoded image prefixes (scripts/persist-scan-results.sh:99–134) — Fixed. The case statement is gone. The script now dynamically parses the artifact name by scanning segments from the end to find the version tag, then derives org/image-name from the remaining segments. Unknown prefixes now exit 1 instead of silently continuing.

Workflow-level contents: write (.github/workflows/trivy-scan-published-images.yml:22–24, 65–66) — Fixed. The workflow-level permissions are contents: read / actions: read, and contents: write is scoped to the persist-results job only.

Non-semver version sort (scripts/persist-scan-results.sh:179–195) — Fixed. The updated jq correctly separates semver tags (starting with a digit) from non-semver tags (e.g. latest), sorts semver numerically with pre-release handling via split("-")[0], and appends non-semver tags alphabetically at the end.


LGTM — no significant issues remaining.

@jandroav jandroav requested a review from sayaliM0412 March 16, 2026 21:31
@liquibase liquibase deleted a comment from coderabbitai Bot Mar 16, 2026
@liquibase liquibase deleted a comment from claude Bot Mar 16, 2026
@liquibase liquibase deleted a comment from claude Bot Mar 16, 2026
@liquibase liquibase deleted a comment from claude Bot Mar 16, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 16, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 048645be-a954-4920-bc79-404f97f48f4e

📥 Commits

Reviewing files that changed from the base of the PR and between 5e95ccd and 3b74b31.

📒 Files selected for processing (2)
  • .github/workflows/trivy-scan-published-images.yml
  • scripts/persist-scan-results.sh

📝 Walkthrough

Walkthrough

This PR adds a GitHub Actions job and a new script to persist Trivy/Grype vulnerability scan artifacts into a dedicated scan-results branch, organizing results by organization, image, and tag and maintaining a manifest with per-image/tag metadata and scan status.

Changes

Cohort / File(s) Summary
Workflow: persist job
.github/workflows/trivy-scan-published-images.yml
Adds persist-results job (needs: generate-matrix, vulnerability-scan) that always runs unless cancelled, checks out repo, downloads vulnerability-report-* artifacts, sets EXPECTED_MATRIX, and runs scripts/persist-scan-results.sh with contents: write permission.
Scan persistence script
scripts/persist-scan-results.sh
New Bash script that validates artifact dir, uses a Git worktree for scan-results branch (creates orphan if missing), copies trivy-*/grype-results.json into org/image/tag paths, writes metadata.json, updates manifest.json (semver-sorted tags), handles EXPECTED_MATRIX to mark missing scans, commits and pushes changes.

Sequence Diagram

sequenceDiagram
    participant GHA as GitHub Actions
    participant Artifacts as Artifact Storage
    participant Checkout as Repo Checkout
    participant Worktree as Git Worktree
    participant FS as File System

    GHA->>Checkout: actions/checkout
    GHA->>Artifacts: download vulnerability-report-* artifacts
    Artifacts-->>GHA: artifacts downloaded
    GHA->>FS: extract scan-artifacts/
    GHA->>Worktree: check or create scan-results branch (worktree)
    alt branch exists
        Worktree->>Checkout: fetch & checkout branch
    else branch missing
        Worktree->>Worktree: create orphan branch + init manifest
    end
    loop per artifact (vulnerability-report-*)
        FS->>FS: parse org/image:tag from dir name
        FS->>FS: copy trivy/grype results into org/image/tag
        FS->>FS: write metadata.json and update manifest in-memory
    end
    FS->>FS: mark missing expected scans (EXPECTED_MATRIX) as failed
    FS->>FS: sort tags (semver desc), update manifest.lastUpdated
    FS->>Worktree: stage changes
    Worktree->>Checkout: commit & push to scan-results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

type: ci

Suggested reviewers

  • jnewton03
  • sayaliM0412
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding persistence of vulnerability scan results to a dedicated branch, which aligns with the substantial changes in the workflow and new script.
Description check ✅ Passed The description is well-related to the changeset, explaining the purpose, structure, changes made, and providing a test plan that directly corresponds to the workflow and script additions.
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
  • Commit unit tests in branch DAT-21268-persist-scan-results
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 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/trivy-scan-published-images.yml:
- Around line 60-63: The persist-results job currently runs on failure and
skipped legs because its if condition uses always() &&
needs.vulnerability-scan.result != 'cancelled'; change this to gate on full
success by replacing the condition with needs.vulnerability-scan.result ==
'success' (or alternatively add explicit handling to record failures for each
matrix leg), so the "persist-results" job (job id persist-results / name Persist
Scan Results) only runs when the entire vulnerability-scan job set succeeded and
does not publish a fresh manifest after a failed scan matrix.

In `@scripts/persist-scan-results.sh`:
- Around line 207-213: The current push to origin "$BRANCH" can fail with a
non-fast-forward when concurrent runs race; add a retry path around the git push
that fetches and rebases the latest origin branch and retries the push (e.g.
loop up to N attempts: git fetch origin "$BRANCH", git rebase origin/"$BRANCH"
(or git pull --rebase origin "$BRANCH"), resolve/retry the commit/push, with
short backoff between attempts) and preserve CHANGED_FILES/CHANGED_COUNT logic;
reference the variables CHANGED_FILES, CHANGED_COUNT and BRANCH and ensure the
final push only exits with failure after retries are exhausted.
- Around line 145-169: The script currently copies available files in the FILE
loop but proceeds to write metadata.json, git add DEST_DIR and update MANIFEST
even if some scanner JSONs are missing, producing mixed old/new results; change
the logic to first verify presence of all required JSON artifacts (e.g.,
trivy-surface.json, trivy-deep.json, grype-results.json or a JSON variant of
grype) under ARTIFACT_PATH and abort with a non-zero exit (and no git/manifest
changes) if any are missing, or alternatively stage copies into a temporary
directory and only move/commit DEST_DIR, write metadata.json and update MANIFEST
(using SCANNED_AT, IMAGE_PATH, TAG, GITHUB_RUN_ID and MANIFEST) after the
complete set has been validated; ensure no partial writes occur when exiting.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e06059e2-1e51-416d-9e87-a65bca435668

📥 Commits

Reviewing files that changed from the base of the PR and between 821fe47 and 5e95ccd.

📒 Files selected for processing (2)
  • .github/workflows/trivy-scan-published-images.yml
  • scripts/persist-scan-results.sh

Comment on lines +60 to +63
persist-results:
name: Persist Scan Results
needs: vulnerability-scan
if: always() && needs.vulnerability-scan.result != 'cancelled'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't publish a fresh manifest after a failed scan matrix.

This condition still runs on failure and skipped. Because the script rewrites lastUpdated and only updates the artifacts it sees, a broken scan leg leaves stale data in scan-results while making the manifest look current. If partial persistence is intentional, record failures explicitly; otherwise gate this job on full success.

Suggested fix
   persist-results:
     name: Persist Scan Results
     needs: vulnerability-scan
-    if: always() && needs.vulnerability-scan.result != 'cancelled'
+    if: needs.vulnerability-scan.result == 'success'
📝 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
persist-results:
name: Persist Scan Results
needs: vulnerability-scan
if: always() && needs.vulnerability-scan.result != 'cancelled'
persist-results:
name: Persist Scan Results
needs: vulnerability-scan
if: needs.vulnerability-scan.result == 'success'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/trivy-scan-published-images.yml around lines 60 - 63, The
persist-results job currently runs on failure and skipped legs because its if
condition uses always() && needs.vulnerability-scan.result != 'cancelled';
change this to gate on full success by replacing the condition with
needs.vulnerability-scan.result == 'success' (or alternatively add explicit
handling to record failures for each matrix leg), so the "persist-results" job
(job id persist-results / name Persist Scan Results) only runs when the entire
vulnerability-scan job set succeeded and does not publish a fresh manifest after
a failed scan matrix.

Comment thread scripts/persist-scan-results.sh Outdated
Comment on lines +145 to +169
for FILE in trivy-surface.json trivy-deep.json grype-results.json; do
if [ -f "$ARTIFACT_PATH/$FILE" ]; then
cp "$ARTIFACT_PATH/$FILE" "$DEST_DIR/$FILE"
fi
done

# Also check for grype-results.json variants (some workflows output grype-results.sarif too)
# We only need the JSON

# Create metadata.json
cat > "$DEST_DIR/metadata.json" <<EOF
{
"scannedAt": "$SCANNED_AT",
"image": "$IMAGE_PATH",
"tag": "$TAG",
"workflowRunId": "${GITHUB_RUN_ID:-}"
}
EOF

git add "$DEST_DIR/"

# Update manifest in memory — add tag to image list if not already present
MANIFEST=$(echo "$MANIFEST" | jq --arg img "$IMAGE_PATH" --arg tag "$TAG" '
.images[$img] = ((.images[$img] // []) | if index($tag) then . else . + [$tag] end)
')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Require a complete scan artifact before publishing a version.

This block silently skips missing JSON files, then still writes fresh metadata.json and updates manifest.json. On reruns that also means an old scanner output can remain in scan-results beside new files, so the dashboard sees a mixed old/new result as if it were complete.

Suggested fix
-  for FILE in trivy-surface.json trivy-deep.json grype-results.json; do
-    if [ -f "$ARTIFACT_PATH/$FILE" ]; then
-      cp "$ARTIFACT_PATH/$FILE" "$DEST_DIR/$FILE"
-    fi
-  done
+  MISSING_FILES=()
+  for FILE in trivy-surface.json trivy-deep.json grype-results.json; do
+    if [ -f "$ARTIFACT_PATH/$FILE" ]; then
+      cp "$ARTIFACT_PATH/$FILE" "$DEST_DIR/$FILE"
+    else
+      MISSING_FILES+=("$FILE")
+    fi
+  done
+
+  if [ "${`#MISSING_FILES`[@]}" -gt 0 ]; then
+    echo "Error: artifact $ARTIFACT_NAME is missing required files: ${MISSING_FILES[*]}" >&2
+    exit 1
+  fi
📝 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
for FILE in trivy-surface.json trivy-deep.json grype-results.json; do
if [ -f "$ARTIFACT_PATH/$FILE" ]; then
cp "$ARTIFACT_PATH/$FILE" "$DEST_DIR/$FILE"
fi
done
# Also check for grype-results.json variants (some workflows output grype-results.sarif too)
# We only need the JSON
# Create metadata.json
cat > "$DEST_DIR/metadata.json" <<EOF
{
"scannedAt": "$SCANNED_AT",
"image": "$IMAGE_PATH",
"tag": "$TAG",
"workflowRunId": "${GITHUB_RUN_ID:-}"
}
EOF
git add "$DEST_DIR/"
# Update manifest in memory — add tag to image list if not already present
MANIFEST=$(echo "$MANIFEST" | jq --arg img "$IMAGE_PATH" --arg tag "$TAG" '
.images[$img] = ((.images[$img] // []) | if index($tag) then . else . + [$tag] end)
')
MISSING_FILES=()
for FILE in trivy-surface.json trivy-deep.json grype-results.json; do
if [ -f "$ARTIFACT_PATH/$FILE" ]; then
cp "$ARTIFACT_PATH/$FILE" "$DEST_DIR/$FILE"
else
MISSING_FILES+=("$FILE")
fi
done
if [ "${`#MISSING_FILES`[@]}" -gt 0 ]; then
echo "Error: artifact $ARTIFACT_NAME is missing required files: ${MISSING_FILES[*]}" >&2
exit 1
fi
# Also check for grype-results.json variants (some workflows output grype-results.sarif too)
# We only need the JSON
# Create metadata.json
cat > "$DEST_DIR/metadata.json" <<EOF
{
"scannedAt": "$SCANNED_AT",
"image": "$IMAGE_PATH",
"tag": "$TAG",
"workflowRunId": "${GITHUB_RUN_ID:-}"
}
EOF
git add "$DEST_DIR/"
# Update manifest in memory — add tag to image list if not already present
MANIFEST=$(echo "$MANIFEST" | jq --arg img "$IMAGE_PATH" --arg tag "$TAG" '
.images[$img] = ((.images[$img] // []) | if index($tag) then . else . + [$tag] end)
')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/persist-scan-results.sh` around lines 145 - 169, The script currently
copies available files in the FILE loop but proceeds to write metadata.json, git
add DEST_DIR and update MANIFEST even if some scanner JSONs are missing,
producing mixed old/new results; change the logic to first verify presence of
all required JSON artifacts (e.g., trivy-surface.json, trivy-deep.json,
grype-results.json or a JSON variant of grype) under ARTIFACT_PATH and abort
with a non-zero exit (and no git/manifest changes) if any are missing, or
alternatively stage copies into a temporary directory and only move/commit
DEST_DIR, write metadata.json and update MANIFEST (using SCANNED_AT, IMAGE_PATH,
TAG, GITHUB_RUN_ID and MANIFEST) after the complete set has been validated;
ensure no partial writes occur when exiting.

Comment on lines +207 to +213
CHANGED_FILES=$(git diff --cached --name-only)
CHANGED_COUNT=0
if [ -n "$CHANGED_FILES" ]; then
CHANGED_COUNT=$(echo "$CHANGED_FILES" | grep -c "metadata.json" || true)
fi
git commit -m "Update scan results ($CHANGED_COUNT version(s)) — $SCANNED_AT"
git push origin "$BRANCH"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

This push path still races across workflow runs.

Every run commits locally and pushes straight to the same scan-results branch. If two runs reach this block together, one of them can lose the update on a plain non-fast-forward rejection, because git push refuses to update a branch that is no longer an ancestor of the commit being pushed. Either serialize the writer job or add a retry path that refreshes from origin/$BRANCH before pushing. (git-scm.com)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/persist-scan-results.sh` around lines 207 - 213, The current push to
origin "$BRANCH" can fail with a non-fast-forward when concurrent runs race; add
a retry path around the git push that fetches and rebases the latest origin
branch and retries the push (e.g. loop up to N attempts: git fetch origin
"$BRANCH", git rebase origin/"$BRANCH" (or git pull --rebase origin "$BRANCH"),
resolve/retry the commit/push, with short backoff between attempts) and preserve
CHANGED_FILES/CHANGED_COUNT logic; reference the variables CHANGED_FILES,
CHANGED_COUNT and BRANCH and ensure the final push only exits with failure after
retries are exhausted.

Comment thread .github/workflows/trivy-scan-published-images.yml
Comment thread scripts/persist-scan-results.sh Outdated
When some vulnerability scan matrix jobs fail, the persist step now
compares expected matrix entries against actual artifacts and writes
status "failed" metadata for missing scans instead of silently omitting
them. Adds scan_status to manifest.json for dashboard visibility.
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 18, 2026

Claude finished @jandroav's task in 1m 57s —— View job


Review: DAT-21268 latest changes

  • Gather context
  • Read changed files
  • Post findings

The EXPECTED_MATRIX approach for recording failed scans is a good design — the dashboard can now show "status": "failed" for matrix legs that produced no artifact. Two issues remain from prior rounds:

  1. Partial artifact still writes "status": "success" — the EXPECTED_MATRIX check only catches completely absent artifact directories; if a scanner's upload partially succeeds (artifact dir exists but one JSON is missing), the entry is still marked "success". See inline comment at line 168. Fix this →

  2. Race condition on push still unaddressed — concurrent runs (common in the scheduled matrix) will have one git push origin "$BRANCH" fail with a non-fast-forward rejection; the commit is lost silently. See inline comment at line 285. Fix this →

Checks for all three required scan files (trivy-surface.json,
trivy-deep.json, grype-results.json) before persisting. Missing files
are logged as warnings and recorded in metadata.json's missing_files
array so the dashboard can flag incomplete scan data.
@jandroav jandroav requested a review from sayaliM0412 March 18, 2026 15:18
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 18, 2026

Claude finished @jandroav's task in 2m 8s —— View job


Review: latest commits (3b74b31, bed7a6f)

  • Gather context
  • Read changed files
  • Review latest commit changes
  • Post findings

Fixes verified ✅

bed7a6f — Failed scans recorded in manifest: The EXPECTED_MATRIX comparison correctly detects missing artifacts and writes metadata.json with "status": "failed" + updates manifest.json's scan_status object. The workflow if: always() && ... != 'cancelled' is intentional here (allows persist to run on partial scan failure to record the failures).

3b74b31 — Missing file tracking: The REQUIRED_SCAN_FILES loop now collects missing files into MISSING_FILES[], logs a warning, and includes a "missing_files" array in metadata.json.


One remaining issue

status: "success" with missing files (scripts/persist-scan-results.sh:179) — When scan files are partially missing, metadata.json still shows "status": "success" alongside a non-empty "missing_files" array. The dashboard has to inspect two fields to determine completeness; using "partial" as a third status value would make this unambiguous.

Comment thread scripts/persist-scan-results.sh
Comment thread scripts/persist-scan-results.sh
Comment thread scripts/persist-scan-results.sh
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 19, 2026

Claude finished @jandroav's task in 1m 52s —— View job


Review: DAT-21268 latest changes

  • Gather context
  • Read changed files
  • Implement remaining fixes
  • Commit and push

Two remaining issues from prior review rounds have been fixed in commit 208666d:

  1. status: "partial" for incomplete scans (scripts/persist-scan-results.sh:168–197) — metadata.json now uses "partial" instead of "success" when any required scan file is missing. manifest.json's scan_status is updated consistently with the same value. The dashboard no longer needs to cross-reference status and missing_files to detect incompleteness.

  2. Push retry loop (scripts/persist-scan-results.sh:299–311) — Replaced the bare git push with a loop (up to 3 attempts) that fetches and rebases origin/scan-results on non-fast-forward failure, preventing silent data loss when concurrent scheduled runs race to push.

No other significant issues found.

@jandroav jandroav merged commit 311cf03 into main Mar 19, 2026
7 checks passed
@jandroav jandroav deleted the DAT-21268-persist-scan-results branch March 19, 2026 06:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants