Skip to content

feat: add early-access build pipeline for main branch#398

Merged
soul2zimate merged 2 commits into
guacsec:mainfrom
soul2zimate:early-access
Apr 8, 2026
Merged

feat: add early-access build pipeline for main branch#398
soul2zimate merged 2 commits into
guacsec:mainfrom
soul2zimate:early-access

Conversation

@soul2zimate

@soul2zimate soul2zimate commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

Adds a Stage workflow that automatically produces a rolling GitHub pre-release tagged early-access on every push to main, giving QE a way to test latest changes without waiting for a formal release.

Resolves #397

Adds a Stage workflow that automatically produces a rolling GitHub
pre-release tagged `early-access` on every push to main, giving users
a way to test latest changes without waiting for a formal release.

Resolves guacsec#397

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Add early-access build pipeline for main branch

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Adds automated early-access build pipeline for main branch
• Builds project with Maven and uploads JAR artifacts
• Creates rolling GitHub pre-release with checksums
• Enables users to test latest changes without formal release
Diagram
flowchart LR
  push["Push to main"] --> build["Build with Maven"]
  build --> upload["Upload JAR artifacts"]
  upload --> check["Check existing release"]
  check --> delete["Delete old release"]
  delete --> sleep["Sleep 5 seconds"]
  sleep --> create["Create new pre-release"]
  create --> checksum["Generate SHA256 checksums"]
  checksum --> assets["Upload assets to release"]
Loading

Grey Divider

File Changes

1. .github/workflows/stage.yml ✨ Enhancement +134/-0

Early-access rolling release workflow

• New workflow triggered on push to main or manual dispatch
• Build job compiles project with Maven and uploads JAR artifacts
• Release job manages rolling early-access pre-release lifecycle
• Deletes previous release/tag, creates new pre-release with checksums
• Uploads JAR files and SHA256 checksums as release assets

.github/workflows/stage.yml


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX Issues (0)

Grey Divider


Action required

1. Curl upload hides HTTP errors🐞
Description
The asset upload loop uses curl without failing on HTTP 4xx/5xx and without checking response
codes, so the job can succeed while assets fail to upload, producing an incomplete early-access
release. This can silently ship a release with missing binaries/checksums.
Code

.github/workflows/stage.yml[R123-133]

+      - name: Upload packages and checksums as early-access release assets
+        working-directory: distributions
+        run: |
+          for file in *
+          do
+            asset_name=$(basename "$file")
+            upload_url=$(echo "${{ steps.new_release.outputs.upload_url }}" | sed "s/{?name,label}/?name=$asset_name/g")
+            curl --data-binary @"$file" \
+            -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
+            -H "Content-Type: application/octet-stream" \
+            "$upload_url"
Evidence
The Stage workflow uploads release assets with a plain curl --data-binary invocation and does not
enable HTTP-failure exit codes (--fail) or validate HTTP status. Elsewhere in the repo, curl calls
explicitly capture and check HTTP status codes, demonstrating that this repo already expects to gate
behavior on HTTP response codes.

.github/workflows/stage.yml[123-133]
.github/workflows/release.yml[69-85]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The release asset upload loop uses `curl` without `--fail` and without checking the HTTP status code. `curl` can return exit code 0 even when the server returns 4xx/5xx, which can make the workflow pass while publishing an incomplete release.

## Issue Context
This step is the final publication step; silent failure defeats the purpose of having a CI-produced early-access release.

## Fix Focus Areas
- .github/workflows/stage.yml[123-133]

## Suggested fix
Harden the upload loop:
- Add `set -euo pipefail` at the start of the `run` block.
- Use `curl --fail-with-body --retry 3 --retry-all-errors --show-error --silent` (or similar) and check the exit code.
- Optionally, switch to a dedicated release asset upload action (e.g., `softprops/action-gh-release@v2` as used elsewhere) to avoid hand-rolling upload URL handling and error checking.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Cancellation breaks early-access release🐞
Description
The workflow enables cancel-in-progress: true while the release job performs destructive updates
(delete tag/release, recreate, then upload assets); if a newer push cancels a run mid-update, the
repo can be left with no early-access release/tag or with a release missing assets. This is
especially risky because these operations mutate a shared, global tag/release rather than producing
per-run artifacts.
Code

.github/workflows/stage.yml[R12-15]

+concurrency:
+  group: stage-${{ github.ref }}
+  cancel-in-progress: true
+
Evidence
cancel-in-progress: true will interrupt any in-flight Stage run, but the Stage run’s release job
deletes and recreates the shared early-access release/tag and then uploads assets; canceling
between those steps can leave the shared release in an inconsistent state. The repo’s existing
release workflow explicitly avoids cancel-in-progress, indicating release mutations are expected to
be non-interruptible.

.github/workflows/stage.yml[12-15]
.github/workflows/stage.yml[61-134]
.github/workflows/release.yml[12-14]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The workflow-level concurrency uses `cancel-in-progress: true`, but the `release` job performs a destructive, multi-step update of the shared `early-access` tag/release. If a run is canceled after deleting the existing release/tag or after creating the new release but before uploading assets, users can observe a missing or incomplete early-access release.

## Issue Context
This is a “delete then recreate then upload” sequence that is not atomic and should not be interruptible.

## Fix Focus Areas
- .github/workflows/stage.yml[12-15]
- .github/workflows/stage.yml[61-134]

## Suggested fix
Choose one:
1) Set `cancel-in-progress: false` for this workflow’s concurrency so runs queue instead of being canceled.
2) Split concurrency: allow cancelation for `build`, but make `release` non-cancelable by moving concurrency control to job level (or use a separate concurrency group for `release` with `cancel-in-progress: false`).
3) Make the release update more atomic/idempotent (e.g., create/update without deleting first, or upload assets before swapping the tag).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Release lookup masks non-404 failures🐞
Description
The “check for existing early-access release” step uses continue-on-error: true, so non-404
failures (rate limit/auth/transient API errors) are suppressed and treated as “no output,” making
the workflow proceed without any visibility into why the lookup failed. This reduces debuggability
and can cause later steps to behave incorrectly relative to the actual existing release state.
Code

.github/workflows/stage.yml[R61-71]

+      - name: Check for existing early-access release
+        id: existing_release
+        uses: actions/github-script@v7
+        continue-on-error: true
+        with:
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+          script: |
+            const repo_name = context.payload.repository.full_name
+            var response = await github.request('GET /repos/' + repo_name + '/releases/tags/early-access')
+            // if the request fails (ie 404) the next steps will not occur and the output will not be set
+            core.setOutput('id', response.data.id)
Evidence
The existing-release lookup explicitly suppresses all errors via continue-on-error: true, and the
script does not log status codes or distinguish 404 from other failures; any failure results in no
id output being set, which hides the underlying cause from logs and subsequent conditional logic.

.github/workflows/stage.yml[61-71]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The workflow suppresses all errors when querying the existing `early-access` release. That makes non-404 failures (e.g., API issues) indistinguishable from “release not found,” and the workflow continues without clear diagnostics.

## Issue Context
The intent is to ignore the specific “not found” case, not to hide all GitHub API failures.

## Fix Focus Areas
- .github/workflows/stage.yml[61-71]

## Suggested fix
Replace `continue-on-error: true` with explicit error handling inside `actions/github-script`:
- Wrap the request in try/catch.
- If status is 404, set output `id` to empty and continue.
- For any other error, log the status/message and `core.setFailed(...)` so the workflow fails with an actionable message.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread .github/workflows/stage.yml Outdated
Comment thread .github/workflows/stage.yml
- Split concurrency to job level: build is cancelable, release is not,
  preventing incomplete early-access releases from mid-flight cancellation
- Replace blanket continue-on-error with explicit 404 handling when
  checking for existing releases, so real API failures are surfaced
- Add set -euo pipefail and curl --fail-with-body --retry to asset
  uploads so server errors fail the workflow instead of passing silently

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@soul2zimate

Copy link
Copy Markdown
Contributor Author

/review

@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis ✅

397 - PR Code Verified

Compliant requirements:

  • Add a Stage GitHub Actions workflow at .github/workflows/stage.yml.
  • The workflow automatically produces early-access builds from the main branch.

Requires further human verification:

  • Confirm the workflow successfully runs on main in the real repo and produces an early-access pre-release with assets attached.
  • Confirm the chosen action versions resolve correctly on GitHub Actions runners (no marketplace/version typos).
⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Invalid Actions Versions

Several uses: references appear to point to non-existent major versions (e.g., actions/checkout@v6, actions/upload-artifact@v7). If these versions don’t exist in the marketplace, the workflow will fail at runtime.

- name: Checkout sources
  uses: actions/checkout@v6

- name: Set up Java 21
  uses: actions/setup-java@v5
  with:
    distribution: temurin
    java-version: 21
    cache: maven

- name: Build with Maven
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  run: mvn package -B -DskipTests -Dskip.junit_platform=true

- name: Upload JAR artifacts
  uses: actions/upload-artifact@v7
  with:
    name: distributions
    path: |
      ./target/trustify-da-java-client-*.jar
      !./target/original-*.jar
Script Syntax Bug

The “Delete early-access release” step interpolates a GitHub Actions expression directly into JavaScript (... + ${{ steps.existing_release.outputs.id }}), which can produce invalid JS and/or fail when the output is empty. Pass the value via env: or as a string and parse it inside the script.

- name: Delete early-access release if exists
  if: ${{ steps.existing_release.outputs.id }}
  uses: actions/github-script@v7
  with:
    github-token: ${{ secrets.GITHUB_TOKEN }}
    script: |
      const repo_name = context.payload.repository.full_name
      await github.request('DELETE /repos/' + repo_name + '/releases/' + ${{ steps.existing_release.outputs.id }})
Release API Inputs

The release creation payload uses make_latest: 'false' as a string; the GitHub API expects a boolean (or specific allowed values depending on API version). Validate the API contract to avoid creating an unexpected “latest” release state or API errors.

- name: Create new early-access release
  id: new_release
  uses: actions/github-script@v7
  with:
    github-token: ${{ secrets.GITHUB_TOKEN }}
    script: |
      const repo_name = context.payload.repository.full_name
      const response = await github.request('POST /repos/' + repo_name + '/releases', {
        tag_name: 'early-access',
        name: 'Early-Access',
        draft: false,
        prerelease: true,
        generate_release_notes: true,
        make_latest: 'false'
      })
      core.setOutput('upload_url', response.data.upload_url)

@soul2zimate
soul2zimate requested a review from ruromero April 8, 2026 07:05
@soul2zimate
soul2zimate merged commit 90c80d6 into guacsec:main Apr 8, 2026
38 checks passed
@soul2zimate
soul2zimate deleted the early-access branch April 8, 2026 09:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Early-Access Build

2 participants