feat(release): cutover G7 + G8 + G9 (GitHub Releases pivot) — bun-build + cosign sign + gh release publish#84
Conversation
…(cutover G7 extraction)
Surgical port of cutover wish G7 from origin/wish/autopg-cutover-transport-absorb
onto main. All target paths conflict-free (none existed on main); files copied
verbatim from cutover branch.
What ships:
- scripts/build-binary.sh: bun build --compile static binaries for 5 platforms
(linux-x64-glibc, linux-x64-musl, linux-arm64, darwin-x64, darwin-arm64).
Fallback to pkg/nexe via AUTOPG_BUILD_FALLBACK=1 per wish G7 contract.
- scripts/fetch-postgres-bins.sh: stages PostgreSQL server binaries per
platform under dist/<platform>/autopg/postgres/{bin,share}/ for self-
contained release tarballs.
- scripts/assemble-tarball.sh: packs binary + bundled postgres + LICENSE etc
into a single per-platform tarball.
- tests/integration/tarball-smoke.sh: smoke test fixture validating tarball
structure + autopg --version exit code.
- .github/workflows/build-tarballs.yml: CI matrix wiring.
Validation:
- bash -n on all 4 scripts: syntax clean.
- bun run lint: clean.
- bun run lint:audit: scanned 32 files, 0 issues.
Cohort: cutover wish unique extractions onto main (post-v2.5.0). Follows
G6 (PR #83 merged). Sets up release infrastructure for G8 (cosign sign +
SLSA L3), G9 (CDN publish), G10 (install.sh) follow-on PRs.
Note: G5 (autopg create-app + manifest LOCK 1) deferred — has structural
entanglement with admin-bootstrap.js + autopg_meta schema infrastructure
that doesn't exist on main; needs proper engineering effort, not file copy.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughAdds a complete multi-platform build-and-release pipeline for autopg: compile binaries (with fallbacks), stage Postgres runtimes, create deterministic tarballs with per-file manifests and outer checksums, sign and attest artifacts, aggregate manifests, verify published artifacts, and CI workflows plus smoke tests. ChangesTarball Assembly & Distribution Pipeline
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a suite of Bash scripts and integration tests to automate the build, staging, and assembly of platform-specific autopg distribution tarballs. The scripts handle static binary compilation with fallbacks, PostgreSQL binary retrieval, and deterministic tarball creation. The review feedback highlights a recurring issue where error handling is bypassed because functions are called within OR lists in the main entry points, suppressing the set -e behavior. Actionable suggestions were provided to explicitly handle failures in these critical steps to prevent the production of broken artifacts. Additionally, it was recommended to escape file paths in the manifest generation logic to ensure valid JSON output.
| fi | ||
|
|
||
| echo "==> [${platform}] assemble tarball" | ||
| verify_inputs "$stage" "$platform" |
There was a problem hiding this comment.
Since assemble_one is invoked as part of an OR list (|| rc=$?) in the main function, the shell's set -e (exit on error) behavior is suppressed during its execution. Consequently, if verify_inputs fails and returns a non-zero status, the script will continue to execute the subsequent steps (like generating the manifest and rolling the tarball) instead of stopping. You should explicitly handle the failure to prevent producing broken artifacts.
| verify_inputs "$stage" "$platform" | |
| verify_inputs "$stage" "$platform" || return 1 |
|
|
||
| # 1) emit per-file manifest BEFORE the tarball is rolled — manifest is | ||
| # bundled inside. | ||
| emit_manifest "$stage" "$platform" "${stage}/autopg/manifest.json" |
There was a problem hiding this comment.
Similar to the verify_inputs call, any failure in emit_manifest will be ignored due to the suppression of set -e in this context. This could result in a tarball being created with an incomplete or missing manifest.json.
| emit_manifest "$stage" "$platform" "${stage}/autopg/manifest.json" | |
| emit_manifest "$stage" "$platform" "${stage}/autopg/manifest.json" || return 1 |
| tar_flags+=(--owner=0 --group=0 --numeric-owner) | ||
| fi | ||
|
|
||
| tar -C "$stage" -czf "$tarball" "${tar_flags[@]}" autopg/ |
There was a problem hiding this comment.
The tar command's failure will not be caught by set -e because assemble_one is called in a conditional context in main. If tar fails, the script will proceed to calculate the SHA256 of a potentially non-existent or corrupted file.
| tar -C "$stage" -czf "$tarball" "${tar_flags[@]}" autopg/ | |
| tar -C "$stage" -czf "$tarball" "${tar_flags[@]}" autopg/ || return 1 |
| } | ||
|
|
||
| local out_dir="${DIST_DIR}/${platform}/autopg" | ||
| mkdir -p "$out_dir" |
There was a problem hiding this comment.
| echo "==> [${platform}] fetch postgres bins" | ||
|
|
||
| if [[ -n "${AUTOPG_POSTGRES_LOCAL_DIR:-}" ]]; then | ||
| stage_from_local "$AUTOPG_POSTGRES_LOCAL_DIR" "$out_dir" |
There was a problem hiding this comment.
Since fetch_one is called with || rc=$? in main, set -e is suppressed. If stage_from_local fails, the function will continue to the final validation checks at the end of fetch_one rather than exiting early. This could lead to inconsistent states if the validation logic is incomplete.
| stage_from_local "$AUTOPG_POSTGRES_LOCAL_DIR" "$out_dir" | |
| stage_from_local "$AUTOPG_POSTGRES_LOCAL_DIR" "$out_dir" || return 1 |
| local pkg | ||
| pkg="$(embedded_pkg_for "$platform")" || true | ||
| if [[ -n "$pkg" ]]; then | ||
| stage_from_pkg "$pkg" "$PG_VERSION" "$out_dir" |
| if [[ -n "$pkg" ]]; then | ||
| stage_from_pkg "$pkg" "$PG_VERSION" "$out_dir" | ||
| elif [[ -n "${AUTOPG_POSTGRES_URL_TEMPLATE:-}" ]]; then | ||
| stage_from_url "$AUTOPG_POSTGRES_URL_TEMPLATE" "$PG_VERSION" "$platform" "$out_dir" |
There was a problem hiding this comment.
Explicit error handling is required here because set -e is disabled when fetch_one is called as part of an OR list.
| stage_from_url "$AUTOPG_POSTGRES_URL_TEMPLATE" "$PG_VERSION" "$platform" "$out_dir" | |
| stage_from_url "$AUTOPG_POSTGRES_URL_TEMPLATE" "$PG_VERSION" "$platform" "$out_dir" || return 1 |
| else | ||
| printf ',\n' | ||
| fi | ||
| printf ' { "path": "%s", "sha256": "%s", "size": %d }' "$f" "$h" "$sz" |
There was a problem hiding this comment.
The file path $f is inserted directly into the JSON manifest without escaping. If a filename contains a double quote or a backslash, the resulting manifest.json will be syntactically invalid. While the current set of binaries and PostgreSQL files is unlikely to have such characters, it is a best practice to ensure the generated JSON is valid.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f5039dc19c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| scripts/aggregate-manifest.sh \ | ||
| scripts/verify-published-artifacts.sh \ | ||
| scripts/cdn-publish.sh \ |
There was a problem hiding this comment.
Remove non-existent scripts from smoke lint step
This shellcheck invocation includes files that do not exist in this commit (scripts/aggregate-manifest.sh, scripts/verify-published-artifacts.sh, scripts/cdn-publish.sh, and additional integration scripts), so the smoke job exits non-zero before running the actual tarball fixture checks. As written, PR runs of this workflow will fail whenever this job is triggered unless those paths are added or removed from the command.
Useful? React with 👍 / 👎.
| echo " -> source: npm @embedded-postgres/${pkg}@${version}" | ||
| local scratch | ||
| scratch=$(mktemp -d) | ||
| trap 'rm -rf "$scratch"' RETURN |
There was a problem hiding this comment.
Avoid RETURN trap on local scratch variable
This cleanup trap references a function-local variable under set -u, which produces scratch: unbound variable on failure paths and masks the original fetch error (reproducible by forcing an invalid AUTOPG_POSTGRES_PKG_VERSION). The same pattern appears again in stage_from_url, so cleanup should be scoped or unset explicitly to keep error handling deterministic.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
scripts/build-binary.sh (1)
113-113: 💤 Low value
-eq 1on env-string can hard-fail underset -e.
AUTOPG_BUILD_FALLBACKis documented as "Set to 1 to retry…", but if a user sets it totrue/yes,[[ "true" -eq 1 ]]triggers a bash error andset -euo pipefailaborts before the fallback even runs. Prefer string equality or accept any non-empty/non-zero value.♻️ Suggested tweak
- if [[ "$FALLBACK_ENABLED" -eq 1 ]]; then + if [[ "$FALLBACK_ENABLED" == "1" || "$FALLBACK_ENABLED" == "true" ]]; then🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/build-binary.sh` at line 113, Replace the numeric comparison against "$FALLBACK_ENABLED" in the if-condition (the line with if [[ "$FALLBACK_ENABLED" -eq 1 ]];) with a safe string check that accepts common truthy values; e.g. test for non-empty and not "0" or match against "1|true|yes" (case-insensitive) so AUTOPG_BUILD_FALLBACK can be "1", "true", or "yes" without triggering a bash arithmetic error under set -euo pipefail; update the condition around FALLBACK_ENABLED accordingly (keep the same block guarded by that if).scripts/assemble-tarball.sh (1)
96-115: 💤 Low valueManifest JSON is built by string concatenation; consider hardening with
jq(or escape paths).
printf '... "path": "%s"' "$f"produces invalid JSON if$fever contains",\, or a control character. Today the inputs are tightly controlled (autopg + postgres bin/share), so the practical risk is low. If you'd like to make the manifest fully robust to whatever upstream postgres tarballs ship, prefer piping throughjq -R -sor building entries withjq -n.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/assemble-tarball.sh` around lines 96 - 115, The manifest assembly currently uses printf with unescaped "%s" for "path" (inside the loop over variable f) which can produce invalid JSON for filenames containing quotes/backslashes/control chars; replace the manual string-concatenation in the loop (the block that uses h=$(sha256_of "$f"), sz=..., the first flag, and the printf that emits '{ "path": "%s", "sha256": "%s", "size": %d }') with a safe JSON builder: produce one JSON object per file using jq (e.g. jq -n --arg path "$f" --arg sha256 "$h" --argjson size "$sz" '{path:$path,sha256:$sha256,size:$size}') or accumulate the records into jq -s to emit a compressed array, ensuring you still sort with find | sort and write the final array to "$out"; keep the sha256_of, size detection, and the overall surrounding redirection to > "$out" intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/build-tarballs.yml:
- Around line 213-222: The step name "Verify size band (50–80MB)" and the
enforced bounds (the SIZE_MB check using the if condition comparing to 30 and
120) are inconsistent; choose one corrective action: either update the step name
to reflect the actual sanity band "Verify size band (30–120MB)" or tighten the
numeric checks in the if condition to enforce 50–80 (change the lower/upper
bounds used with SIZE_MB). Locate the workflow step named "Verify size band
(50–80MB)" and modify either the human-readable name string or the numeric
comparisons that reference SIZE_MB so they match.
- Around line 152-155: Update the deprecated runner for the darwin-x64 entry:
replace the runner value "macos-13" used with the platform identifier
"darwin-x64" with a currently supported Intel macOS image such as
"macos-15-intel" or "macos-26-intel" (or "macos-latest" if you accept
macOS-26/ARM compatibility), ensuring the "platform: darwin-x64" line remains
unchanged and only the "runner: macos-13" token is updated.
- Around line 170-211: The Resolve version step currently injects
user-controlled values directly into run: shells (via ${{ inputs.version }} and
${{ steps.ver.outputs.version }}) which can lead to command injection; change
the Resolve version step to export the resolved VERSION into the job environment
(use env: to set VERSION from the step output) and update downstream steps
(Build autopg binary, Fetch postgres bins, Assemble tarball, Smoke real tarball)
to reference the environment variable (e.g. $VERSION) instead of embedding ${{ …
}} in the run scripts; ensure the step id "ver" still sets an output and that
all occurrences of --version ${{ steps.ver.outputs.version }} are replaced by
--version $VERSION so the value is passed via env and not interpolated into the
run block.
- Around line 84-95: The workflow fails because shellcheck lists scripts that
aren’t present and two jobs reference missing scripts, and the darwin runner
uses the retired macos-13; fix by removing or gating the missing-script work:
update the shellcheck invocation (the multi-line run under "Lint scripts") to
only include the five scripts added in this PR or wrap it with an if:
hashFiles(...) guard so it no-ops until other scripts exist, and remove or gate
the sign-attest-smoke and cdn-publish-smoke jobs (the jobs named
sign-attest-smoke and cdn-publish-smoke) so they don’t run until their scripts
land; finally change the runner label from macos-13 to macos-15 (replace
macos-13 with macos-15 in the job that builds darwin-x64).
In `@scripts/fetch-postgres-bins.sh`:
- Around line 195-196: The branch that calls stage_from_url with
AUTOPG_POSTGRES_URL_TEMPLATE currently hardcodes "16" for the version, which can
produce wrong/download-failing artifacts; change that call to derive the version
from the resolved PG_VERSION (use PG_VERSION if available) or from
AUTOPG_POSTGRES_URL_VERSION, and if necessary update the fallback to the
pipeline's current default (e.g., 18.3.0-beta.17) so
stage_from_url("$AUTOPG_POSTGRES_URL_TEMPLATE", "<resolved-version>",
"$platform", "$out_dir") always receives the correct/consistent version; refer
to AUTOPG_POSTGRES_URL_TEMPLATE, AUTOPG_POSTGRES_URL_VERSION, stage_from_url and
PG_VERSION when making the change.
---
Nitpick comments:
In `@scripts/assemble-tarball.sh`:
- Around line 96-115: The manifest assembly currently uses printf with unescaped
"%s" for "path" (inside the loop over variable f) which can produce invalid JSON
for filenames containing quotes/backslashes/control chars; replace the manual
string-concatenation in the loop (the block that uses h=$(sha256_of "$f"),
sz=..., the first flag, and the printf that emits '{ "path": "%s", "sha256":
"%s", "size": %d }') with a safe JSON builder: produce one JSON object per file
using jq (e.g. jq -n --arg path "$f" --arg sha256 "$h" --argjson size "$sz"
'{path:$path,sha256:$sha256,size:$size}') or accumulate the records into jq -s
to emit a compressed array, ensuring you still sort with find | sort and write
the final array to "$out"; keep the sha256_of, size detection, and the overall
surrounding redirection to > "$out" intact.
In `@scripts/build-binary.sh`:
- Line 113: Replace the numeric comparison against "$FALLBACK_ENABLED" in the
if-condition (the line with if [[ "$FALLBACK_ENABLED" -eq 1 ]];) with a safe
string check that accepts common truthy values; e.g. test for non-empty and not
"0" or match against "1|true|yes" (case-insensitive) so AUTOPG_BUILD_FALLBACK
can be "1", "true", or "yes" without triggering a bash arithmetic error under
set -euo pipefail; update the condition around FALLBACK_ENABLED accordingly
(keep the same block guarded by that if).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8d0d9237-f9bd-4236-9be1-d7f2dff34c12
📒 Files selected for processing (5)
.github/workflows/build-tarballs.ymlscripts/assemble-tarball.shscripts/build-binary.shscripts/fetch-postgres-bins.shtests/integration/tarball-smoke.sh
| - platform: darwin-x64 | ||
| runner: macos-13 | ||
| - platform: darwin-arm64 | ||
| runner: macos-latest |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
GitHub Actions hosted runner macos-13 deprecation status 2026
💡 Result:
The GitHub Actions hosted runner image macos-13 (macOS Ventura, both x64 and arm64) is fully deprecated and unsupported as of December 2025. Deprecation began on September 22, 2025, with brownout periods in November 2025 to fail jobs using it, and full retirement by December 4-8, 2025 [1][2][3]. As of May 8, 2026, it is no longer available; the official GitHub Docs list current macOS runners as macos-15, macos-26 (arm64), and macos-15-intel/macOS-26-intel (x64), with no mention of macos-13 [4]. Projects continued migrating away in 2026 (e.g., April 2026) [5], and runner-images repo deprecated macOS 13 code by February 2026 [6]. Migrate workflows to macos-latest (macos-26 arm64), macos-15, macos-14 (until Nov 2026 [7]), or macos-15-intel for x64 needs [2].
Citations:
- 1: https://github.blog/changelog/2025-09-19-github-actions-macos-13-runner-image-is-closing-down/
- 2: [macOS] The macOS 13 Ventura based runner images will begin deprecation on September 22nd and will be fully unsupported by December 4th for GitHub and ADO actions/runner-images#13046
- 3: macOS 13 Github runners being removed on 2025-12-08 mitogen-hq/mitogen#1358
- 4: https://docs.github.com/en/actions/reference/runners/github-hosted-runners
- 5: Backport: macOS 13 runner deprecation in GitHub workflows wazuh/wazuh#35408
- 6: https://github.com/actions/runner-images/actions/runs/21716272081
- 7: [macOS] The macOS 14 Sonoma based runner images will begin deprecation on July 6th and will be fully unsupported by November 2nd for GitHub Actions and Azure DevOps actions/runner-images#13518
Update macos-13 runner to a currently supported macOS image.
macos-13 was fully deprecated and retired by GitHub on December 4-8, 2025, and as of May 2026 is no longer available. The darwin-x64 workflow entry using macos-13 will fail to schedule. Replace with macos-15-intel or macos-26-intel for x64 support, or macos-latest (macos-26) if arm64 compatibility is acceptable for the x64 entry.
🧰 Tools
🪛 actionlint (1.7.12)
[error] 153-153: label "macos-13" is unknown. available labels are "windows-latest", "windows-latest-8-cores", "windows-2025", "windows-2025-vs2026", "windows-2022", "windows-11-arm", "ubuntu-slim", "ubuntu-latest", "ubuntu-latest-4-cores", "ubuntu-latest-8-cores", "ubuntu-latest-16-cores", "ubuntu-24.04", "ubuntu-24.04-arm", "ubuntu-22.04", "ubuntu-22.04-arm", "macos-latest", "macos-latest-xlarge", "macos-latest-large", "macos-26-intel", "macos-26-xlarge", "macos-26-large", "macos-26", "macos-15-intel", "macos-15-xlarge", "macos-15-large", "macos-15", "macos-14-xlarge", "macos-14-large", "macos-14", "self-hosted", "x64", "arm", "arm64", "linux", "macos", "windows". if it is a custom label for self-hosted runner, set list of labels in actionlint.yaml config file
(runner-label)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/build-tarballs.yml around lines 152 - 155, Update the
deprecated runner for the darwin-x64 entry: replace the runner value "macos-13"
used with the platform identifier "darwin-x64" with a currently supported Intel
macOS image such as "macos-15-intel" or "macos-26-intel" (or "macos-latest" if
you accept macOS-26/ARM compatibility), ensuring the "platform: darwin-x64" line
remains unchanged and only the "runner: macos-13" token is updated.
| - name: Resolve version | ||
| id: ver | ||
| shell: bash | ||
| run: | | ||
| if [[ -n "${{ inputs.version }}" ]]; then | ||
| VERSION="${{ inputs.version }}" | ||
| else | ||
| VERSION=$(node -p "require('./package.json').version") | ||
| fi | ||
| echo "version=${VERSION}" >> "$GITHUB_OUTPUT" | ||
| echo "Resolved version: ${VERSION}" | ||
|
|
||
| # Stage 1 — compile autopg. | ||
| # Fallback (pkg/nexe) is enabled per the wish G7 contract; bun is | ||
| # the primary, the fallback is documented in build.log. | ||
| - name: Build autopg binary | ||
| shell: bash | ||
| env: | ||
| AUTOPG_BUILD_FALLBACK: '1' | ||
| run: bash scripts/build-binary.sh --platform ${{ matrix.platform }} --version ${{ steps.ver.outputs.version }} | ||
|
|
||
| # Stage 2 — fetch postgres bins. | ||
| # @embedded-postgres covers linux-x64-glibc + darwin-{x64,arm64}. | ||
| # linux-x64-musl + linux-arm64 require AUTOPG_POSTGRES_URL_TEMPLATE | ||
| # (passed via repo secret in production) or AUTOPG_POSTGRES_LOCAL_DIR | ||
| # in a self-hosted runner cache. Failure here is loud, not silent. | ||
| - name: Fetch postgres bins | ||
| shell: bash | ||
| env: | ||
| AUTOPG_POSTGRES_PKG_VERSION: ${{ inputs.pg_pkg_version || '18.3.0-beta.17' }} | ||
| AUTOPG_POSTGRES_URL_TEMPLATE: ${{ vars.AUTOPG_POSTGRES_URL_TEMPLATE }} | ||
| run: bash scripts/fetch-postgres-bins.sh --platform ${{ matrix.platform }} | ||
|
|
||
| # Stage 3 — assemble the tarball + emit outer .sha256. | ||
| - name: Assemble tarball | ||
| shell: bash | ||
| run: bash scripts/assemble-tarball.sh --platform ${{ matrix.platform }} --version ${{ steps.ver.outputs.version }} | ||
|
|
||
| # Stage 4 — gate via the real-mode smoke test. | ||
| - name: Smoke real tarball | ||
| shell: bash | ||
| run: bash tests/integration/tarball-smoke.sh --real --platform ${{ matrix.platform }} --version ${{ steps.ver.outputs.version }} |
There was a problem hiding this comment.
Major: GitHub Actions script injection — pass user-controlled inputs via env:, not ${{ }} interpolation.
inputs.version (workflow_dispatch) and the resolved steps.ver.outputs.version are templated directly into the shell of run: blocks at Lines 174–175, 189, 206, 211 (and 216, 229–230). A maliciously crafted dispatch input (e.g. 1.0.0"; curl …| sh; echo ") is then literally substituted into the script body. Workflow_dispatch is gated by repo permissions, but the pattern is a well-known GHA injection sink that linters/audit tooling will flag.
The standard mitigation is to bind ${{ … }} once into an env: and reference the env var in the shell:
🛡️ Suggested pattern for the version-resolve step
- name: Resolve version
id: ver
shell: bash
+ env:
+ INPUT_VERSION: ${{ inputs.version }}
run: |
- if [[ -n "${{ inputs.version }}" ]]; then
- VERSION="${{ inputs.version }}"
+ if [[ -n "${INPUT_VERSION:-}" ]]; then
+ VERSION="$INPUT_VERSION"
else
VERSION=$(node -p "require('./package.json').version")
fi
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "Resolved version: ${VERSION}"The downstream --version ${{ steps.ver.outputs.version }} usages are lower risk because the value has been captured into the output, but the same env-binding pattern keeps things consistent and lint-clean.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/build-tarballs.yml around lines 170 - 211, The Resolve
version step currently injects user-controlled values directly into run: shells
(via ${{ inputs.version }} and ${{ steps.ver.outputs.version }}) which can lead
to command injection; change the Resolve version step to export the resolved
VERSION into the job environment (use env: to set VERSION from the step output)
and update downstream steps (Build autopg binary, Fetch postgres bins, Assemble
tarball, Smoke real tarball) to reference the environment variable (e.g.
$VERSION) instead of embedding ${{ … }} in the run scripts; ensure the step id
"ver" still sets an output and that all occurrences of --version ${{
steps.ver.outputs.version }} are replaced by --version $VERSION so the value is
passed via env and not interpolated into the run block.
| - name: Verify size band (50–80MB) | ||
| shell: bash | ||
| run: | | ||
| TARBALL="dist/autopg-${{ steps.ver.outputs.version }}-${{ matrix.platform }}.tar.gz" | ||
| SIZE_MB=$(du -m "$TARBALL" | cut -f1) | ||
| echo "Tarball size: ${SIZE_MB}MB" | ||
| if [[ "$SIZE_MB" -lt 30 || "$SIZE_MB" -gt 120 ]]; then | ||
| echo "::error::tarball size out of expected band (got ${SIZE_MB}MB; expected 30-120MB sanity-band)" | ||
| exit 1 | ||
| fi |
There was a problem hiding this comment.
Comment vs check drift on the size-band assertion.
Step name says Verify size band (50–80MB) but the check enforces 30–120MB. Pick one — either tighten the check to 50–80 or rename the step to match the actual sanity band so future readers (and dashboards) aren't misled.
📝 Suggested rename
- - name: Verify size band (50–80MB)
+ - name: Verify size band (30–120MB sanity)📝 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.
| - name: Verify size band (50–80MB) | |
| shell: bash | |
| run: | | |
| TARBALL="dist/autopg-${{ steps.ver.outputs.version }}-${{ matrix.platform }}.tar.gz" | |
| SIZE_MB=$(du -m "$TARBALL" | cut -f1) | |
| echo "Tarball size: ${SIZE_MB}MB" | |
| if [[ "$SIZE_MB" -lt 30 || "$SIZE_MB" -gt 120 ]]; then | |
| echo "::error::tarball size out of expected band (got ${SIZE_MB}MB; expected 30-120MB sanity-band)" | |
| exit 1 | |
| fi | |
| - name: Verify size band (30–120MB sanity) | |
| shell: bash | |
| run: | | |
| TARBALL="dist/autopg-${{ steps.ver.outputs.version }}-${{ matrix.platform }}.tar.gz" | |
| SIZE_MB=$(du -m "$TARBALL" | cut -f1) | |
| echo "Tarball size: ${SIZE_MB}MB" | |
| if [[ "$SIZE_MB" -lt 30 || "$SIZE_MB" -gt 120 ]]; then | |
| echo "::error::tarball size out of expected band (got ${SIZE_MB}MB; expected 30-120MB sanity-band)" | |
| exit 1 | |
| fi |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/build-tarballs.yml around lines 213 - 222, The step name
"Verify size band (50–80MB)" and the enforced bounds (the SIZE_MB check using
the if condition comparing to 30 and 120) are inconsistent; choose one
corrective action: either update the step name to reflect the actual sanity band
"Verify size band (30–120MB)" or tighten the numeric checks in the if condition
to enforce 50–80 (change the lower/upper bounds used with SIZE_MB). Locate the
workflow step named "Verify size band (50–80MB)" and modify either the
human-readable name string or the numeric comparisons that reference SIZE_MB so
they match.
| elif [[ -n "${AUTOPG_POSTGRES_URL_TEMPLATE:-}" ]]; then | ||
| stage_from_url "$AUTOPG_POSTGRES_URL_TEMPLATE" "${AUTOPG_POSTGRES_URL_VERSION:-16}" "$platform" "$out_dir" |
There was a problem hiding this comment.
Stale fallback Postgres version (16) is inconsistent with the rest of the pipeline.
When PG_VERSION is unresolved, this branch substitutes 16 into AUTOPG_POSTGRES_URL_TEMPLATE's {ver} placeholder. Everywhere else (workflow default, package.json optionalDependencies) targets 18.3.0-beta.17, so this fallback is likely to produce a 404 or a wrong-version download for the musl/arm64 paths that depend on the URL template.
Either drop the hardcoded "16", reuse the resolved PG_VERSION (re-running the package.json read here), or align the default with the current pipeline version.
♻️ Suggested fix
- elif [[ -n "${AUTOPG_POSTGRES_URL_TEMPLATE:-}" ]]; then
- stage_from_url "$AUTOPG_POSTGRES_URL_TEMPLATE" "${AUTOPG_POSTGRES_URL_VERSION:-16}" "$platform" "$out_dir"
+ elif [[ -n "${AUTOPG_POSTGRES_URL_TEMPLATE:-}" ]]; then
+ if [[ -z "${AUTOPG_POSTGRES_URL_VERSION:-}" && -z "$PG_VERSION" ]]; then
+ echo "error: cannot resolve postgres version for URL template (set AUTOPG_POSTGRES_URL_VERSION or --postgres-version)" >&2
+ return 1
+ fi
+ stage_from_url "$AUTOPG_POSTGRES_URL_TEMPLATE" "${AUTOPG_POSTGRES_URL_VERSION:-$PG_VERSION}" "$platform" "$out_dir"📝 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.
| elif [[ -n "${AUTOPG_POSTGRES_URL_TEMPLATE:-}" ]]; then | |
| stage_from_url "$AUTOPG_POSTGRES_URL_TEMPLATE" "${AUTOPG_POSTGRES_URL_VERSION:-16}" "$platform" "$out_dir" | |
| elif [[ -n "${AUTOPG_POSTGRES_URL_TEMPLATE:-}" ]]; then | |
| if [[ -z "${AUTOPG_POSTGRES_URL_VERSION:-}" && -z "$PG_VERSION" ]]; then | |
| echo "error: cannot resolve postgres version for URL template (set AUTOPG_POSTGRES_URL_VERSION or --postgres-version)" >&2 | |
| return 1 | |
| fi | |
| stage_from_url "$AUTOPG_POSTGRES_URL_TEMPLATE" "${AUTOPG_POSTGRES_URL_VERSION:-$PG_VERSION}" "$platform" "$out_dir" |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/fetch-postgres-bins.sh` around lines 195 - 196, The branch that calls
stage_from_url with AUTOPG_POSTGRES_URL_TEMPLATE currently hardcodes "16" for
the version, which can produce wrong/download-failing artifacts; change that
call to derive the version from the resolved PG_VERSION (use PG_VERSION if
available) or from AUTOPG_POSTGRES_URL_VERSION, and if necessary update the
fallback to the pipeline's current default (e.g., 18.3.0-beta.17) so
stage_from_url("$AUTOPG_POSTGRES_URL_TEMPLATE", "<resolved-version>",
"$platform", "$out_dir") always receives the correct/consistent version; refer
to AUTOPG_POSTGRES_URL_TEMPLATE, AUTOPG_POSTGRES_URL_VERSION, stage_from_url and
PG_VERSION when making the change.
…er G8 + G9 extraction) Surgical port of cutover wish G8 (cosign keyless OIDC sign + SLSA L3 attest) and G9 (CDN publish to cdn.automagik.dev/autopg/*) from cutover branch onto main. All target paths conflict-free. What ships: - .github/workflows/sign-attest.yml: per-platform cosign sign-blob + attest-build-provenance@v1; aggregates a top-level manifest.json. Runs after build-tarballs (G7) on tag. - .github/workflows/cdn-publish.yml: uploads signed tarballs + manifest + channel pointer to cdn.automagik.dev/autopg/<channel>/<version>/<platform>/. Cache-control headers per wish D7 (immutable for versioned paths, short TTL for latest.json). - tests/integration/sign-attest-smoke.sh: hermetic smoke fixture exercising the cosign + manifest + per-platform attestation pipeline without hitting real Sigstore. Referenced by G7's build-tarballs.yml — without this PR, G7's CI matrix references non-existent files. - tests/integration/cdn-publish.sh: smoke fixture validating CDN publish layout end-to-end against a synthetic local-FS CDN. Validation: - bash -n on both scripts: syntax clean. - bun run lint: clean. - bun run lint:audit: scanned 32 files, 0 issues. Cohort: cutover wish unique extractions onto main. Follows G6 (PR #83 merged) and G7 (PR #84). Closes G8 + G9 gaps from PR #82's audit report. G10 (install.sh ≤80 lines) DEFERRED — main has an existing 123-line install.sh (legacy npm-based pgserve installer). Cutover's 79-line CDN bootstrap installer is a DIFFERENT distribution path (CDN, not npm). Putting both at install.sh collides; needs a separate rename decision (install.sh → install-pgserve.sh, or cutover's at install-autopg.sh). Plus the CDN doesn't actually exist yet (G9 workflow publishes to it but Felipe needs to provision the bucket + DNS first). G5 (autopg create-app + manifest LOCK 1) DEFERRED — structural entanglement with admin-bootstrap.js + autopg_meta schema infrastructure that doesn't exist on main. Needs proper engineering effort, not file copy. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Felipe directive 2026-05-08: pick free alternative with native signature verification. GitHub Releases + 'gh attestation verify' (Sigstore Rekor) satisfies both — zero infra cost, zero maintenance, semver tag immutability built-in. What changes vs the prior G9 cdn.automagik.dev approach: - DROP .github/workflows/cdn-publish.yml - DROP tests/integration/cdn-publish.sh - ADD .github/workflows/release-publish.yml (gh release create + upload, draft/prerelease channel support, latest.json committed to .well-known/ for install.sh consumers) - PATCH build-tarballs.yml: remove refs to non-existent CDN-specific scripts (aggregate-manifest, verify-published-artifacts, scripts/cdn-publish) - PATCH knip.json: ignore 'gh' binary (used by release-publish.yml) What stays unchanged: - G7 build-tarballs.yml smoke (matrix shellcheck + tarball assembly) - G8 sign-attest.yml + sign-attest-smoke.sh (cosign keyless OIDC + SLSA L3 work the same regardless of where binaries are stored) CI red root cause: build-tarballs.yml's shellcheck step referenced 3 scripts that were never copied (CDN-specific helpers from cutover branch). Pivot makes those refs unnecessary. Cohort impact: - G7+G8 ship as-is (storage-agnostic) - G9 pivots to GitHub Releases (~30 LOC vs 191 LOC) - G10 install.sh, when revived, fetches from github.com/namastexlabs/pgserve/releases/download/v<version>/ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…pts + cosign fixture keypair Sign + Attest (fixture) job on PR #84 was red because: 1. tests/fixtures/cosign/{cosign.key,cosign.pub,README.md} were missing — sign-attest-smoke.sh aborts at the fixture-keypair-existence check. 2. scripts/aggregate-manifest.sh was missing — sign-attest-smoke.sh calls it to roll up per-platform signatures into top-level manifest.json. 3. scripts/verify-published-artifacts.sh was missing — sign-attest-smoke.sh calls it to assert verify rejects tampered tarballs / missing-sig siblings. These three scripts/files are STORAGE-AGNOSTIC — they operate on cosign signatures + attestations, which live in Sigstore Rekor regardless of where the tarballs are stored (GitHub Releases, S3, anywhere). The earlier elimination of CDN-specific helpers (cdn-publish.sh) was correct; these three are not CDN-specific despite being named alongside it on the cutover branch. Local validation: - bash tests/integration/sign-attest-smoke.sh → result: pass=15 fail=0 - bash -n + shellcheck on both new scripts: clean - bun run lint: clean - bun run lint:audit: 32 files scanned, 0 issues - bun run deadcode: clean build-tarballs.yml shellcheck list updated to include the two new scripts. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 6
♻️ Duplicate comments (3)
.github/workflows/build-tarballs.yml (3)
140-143:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
macos-13runner is retired — workflow will fail to schedule thedarwin-x64build.This is unchanged from the prior review and still trips actionlint locally. Replace with a current Intel-capable image (
macos-15-intelormacos-13→macos-15-intel/macos-26-intel).🛠️ Proposed fix
- platform: darwin-x64 - runner: macos-13 + runner: macos-15-intelGitHub Actions hosted runner labels available 2026 macos-15-intel🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/build-tarballs.yml around lines 140 - 143, The workflow uses an outdated runner label: update the runner value for the darwin-x64 job (identify the block with platform: darwin-x64 and runner: macos-13) to a current Intel-capable macOS hosted runner such as macos-15-intel (or macos-26-intel) so the darwin-x64 build can be scheduled and actionlint will pass; simply replace runner: macos-13 with runner: macos-15-intel.
201-210:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winStep title says 50–80MB but the check enforces 30–120MB.
Same drift flagged previously and still present. Either rename the step to match the actual sanity band or tighten the bounds.
📝 Proposed rename
- - name: Verify size band (50–80MB) + - name: Verify size band (30–120MB sanity)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/build-tarballs.yml around lines 201 - 210, The CI step titled "Verify size band (50–80MB)" is inconsistent with the enforced bounds (30–120MB); update either the step title or the bounds in the check so they match. Locate the job step using the title string "Verify size band (50–80MB)" and the variables TARBALL and SIZE_MB, then either change the title to reflect the actual sanity band (e.g., "Verify size band (30–120MB)") or tighten the conditional range in the shell run block to match 50–80 (adjust the numeric literals used in the if [[ "$SIZE_MB" -lt ... || "$SIZE_MB" -gt ... ]]; then check). Ensure the echoed error message and any documentation strings are updated to the same bounds.
158-199:⚠️ Potential issue | 🟠 Major | ⚡ Quick winScript-injection sink — bind
${{ inputs.version }}viaenv:instead of interpolating intorun:.
${{ inputs.version }}is templated directly into the shell at lines 162-163, 177, 194, 199, 204, 217-218. A crafted dispatch input (e.g.1.0.0"; curl evil | sh; echo ") is substituted into the script body. Capture the input into an env-bound variable on the resolve step and reference$VERSION(and$INPUT_VERSIONon the resolve step itself) downstream.🛡️ Suggested pattern
- name: Resolve version id: ver shell: bash + env: + INPUT_VERSION: ${{ inputs.version }} run: | - if [[ -n "${{ inputs.version }}" ]]; then - VERSION="${{ inputs.version }}" + if [[ -n "${INPUT_VERSION:-}" ]]; then + VERSION="${INPUT_VERSION}" else VERSION=$(node -p "require('./package.json').version") fi echo "version=${VERSION}" >> "$GITHUB_OUTPUT"Then add
env: { VERSION: ${{ steps.ver.outputs.version }}, PLATFORM: ${{ matrix.platform }} }to each subsequent step and switch therun:lines tobash scripts/build-binary.sh --platform "$PLATFORM" --version "$VERSION"(and the same for the assemble/smoke/size/upload steps).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/build-tarballs.yml around lines 158 - 199, The workflow interpolates untrusted inputs into run: scripts (Resolve version and subsequent steps like "Build autopg binary", "Assemble tarball", and "Smoke real tarball"), enabling script-injection via ${{ inputs.version }} and ${{ matrix.platform }}; capture the resolved value in the Resolve version step outputs (steps.ver.outputs.version) and export it via env on each later step (e.g., add env: { VERSION: ${{ steps.ver.outputs.version }}, PLATFORM: ${{ matrix.platform }} }) then change the run invocations to use the env vars (call scripts/build-binary.sh, scripts/assemble-tarball.sh, tests/integration/tarball-smoke.sh with --platform "$PLATFORM" --version "$VERSION") so no user input is directly templated into run: bodies.
🧹 Nitpick comments (5)
tests/fixtures/cosign/cosign.key (1)
1-11: ⚡ Quick winDocument this fixture key explicitly to avoid scanner alarms and accidental reuse.
This is an encrypted Sigstore key whose password (
autopg-fixture) is hardcoded intests/integration/sign-attest-smoke.sh, so the material is effectively non-secret and only suitable for offline test signing. Secret scanners (e.g., betterleaks already flagged it here) will continue to trip on theBEGIN ENCRYPTED SIGSTORE PRIVATE KEYblock, and there's nothing in-tree telling future maintainers it must never be used for real signing. Add a siblingREADME.md(and/or a scanner allowlist entry) intests/fixtures/cosign/calling out: (a) test-only purpose, (b) the fixed password, (c) the matchingcosign.pub, (d) explicit "do not reuse for production" warning.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/fixtures/cosign/cosign.key` around lines 1 - 11, Create a sibling README.md next to the cosign key fixture (tests/fixtures/cosign/) that documents the encrypted Sigstore key in cosign.key: state it is test-only/offline, include the fixed password "autopg-fixture" (used by tests/integration/sign-attest-smoke.sh), reference the matching public key file name (cosign.pub), and add a clear "DO NOT USE IN PRODUCTION / DO NOT REUSE" warning; also mention the purpose of the key (offline test signing) and suggest adding a scanner allowlist entry for this fixture if your repo uses secret scanners..github/workflows/release-publish.yml (2)
139-140: 💤 Low valueBehavior on tag-push trigger:
!inputs.draftevaluates totrue(becauseinputs.draftis undefined onpush).In GitHub expressions
!null === true, so on apush: tags: v*trigger this guard reduces tochannel == 'stable', meaning every stable tag push will commit-and-push.well-known/latest.json. That appears intentional (the channel pointer should advance on stable tag), but worth confirming — if you want tag pushes to also require a non-draft release, add an explicitinputs.draft != true && github.event_name == 'workflow_dispatch'clause, or compute the effective draft flag upstream.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/release-publish.yml around lines 139 - 140, The current `if` condition on the "Update latest.json channel pointer" step uses `if: ${{ steps.ch.outputs.channel == 'stable' && !inputs.draft }}` which treats undefined `inputs.draft` as true on tag-push events; update the condition to explicitly require a non-draft release or that the workflow was manually dispatched. Replace the guard with an explicit check such as ensuring `inputs.draft != true` and/or `github.event_name == 'workflow_dispatch'` (e.g., require both `steps.ch.outputs.channel == 'stable' && inputs.draft != true && github.event_name == 'workflow_dispatch'`) so tag-push triggers don't unintentionally pass when `inputs.draft` is undefined.
130-137: ⚡ Quick win
gh release upload … dist/*will also try to uploadbuild.logandbuild-record.tsv.
build-tarballs.yml(line 219-220) bundlesdist/build.loganddist/build-record.tsvinto each per-platform artifact. After the merged download those land indist/alongside the tarballs and get attached to the public release. That's likely not what you want for end-users (and--clobberacross 5 platforms means each platform'sbuild.logoverwrites the last). Filter the upload glob to release-bound assets only.🛠️ Proposed fix
- if [ -d dist ] && [ "$(ls -A dist 2>/dev/null)" ]; then - gh release upload "v${VERSION}" \ - --repo "${{ github.repository }}" \ - --clobber \ - dist/* - else + shopt -s nullglob + ASSETS=( dist/autopg-*.tar.gz dist/autopg-*.tar.gz.sha256 \ + dist/autopg-*.tar.gz.sig dist/autopg-*.tar.gz.intoto.jsonl \ + dist/manifest.json ) + if [ "${`#ASSETS`[@]}" -gt 0 ]; then + gh release upload "v${VERSION}" \ + --repo "${{ github.repository }}" \ + --clobber \ + "${ASSETS[@]}" + else echo "::warning::dist/ is empty — no assets to upload" fi🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/release-publish.yml around lines 130 - 137, The release upload currently uses the glob "dist/*" with gh release upload (and --clobber), which causes non-release files like build.log and build-record.tsv (produced by build-tarballs.yml) to be attached and overwritten across platforms; change the gh release upload invocation to only upload explicit release asset patterns (e.g. tarball/zip/deb package globs or a whitelist) or explicitly exclude build.log and build-record.tsv so only the intended per-platform artifacts are uploaded, and keep --clobber semantics aligned with the narrowed glob.tests/integration/sign-attest-smoke.sh (1)
116-127: 💤 Low valueManifest assertions are coupled to whitespace in the JSON layout.
grep -q '"version": "${VERSION}"'andgrep -q '"platform": "${p}"'will silently fail ifscripts/aggregate-manifest.shever emits the JSON with different spacing (e.g., compact"version":"…"or extra whitespace). Prefer a structural check viajq(already a near-universal CI dep) so the smoke test stays accurate when the formatter changes.♻️ Suggested rewrite
- if grep -q "\"version\": \"${VERSION}\"" "${WORK_DIR}/manifest.json"; then + if [[ "$(jq -r '.version' "${WORK_DIR}/manifest.json")" == "${VERSION}" ]]; then ok "manifest.json carries version" else bad "manifest.json version field missing" fi for p in "${PLATFORMS[@]}"; do - if grep -q "\"platform\": \"${p}\"" "${WORK_DIR}/manifest.json"; then + if jq -e --arg p "${p}" '[.. | objects | .platform? // empty] | index($p)' \ + "${WORK_DIR}/manifest.json" >/dev/null; then ok "manifest.json lists ${p}" else bad "manifest.json missing ${p}" fi done🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/integration/sign-attest-smoke.sh` around lines 116 - 127, Replace the fragile grep-based JSON checks in the sign-attest-smoke.sh test with structural jq queries: read "${WORK_DIR}/manifest.json" and use jq to compare .version to the shell $VERSION variable and to test that each element of the shell PLATFORMS array exists in .platforms (or the appropriate array key) before calling ok/bad; update the blocks that currently use grep -q "\"version\": \"${VERSION}\"" and grep -q "\"platform\": \"${p}\"" to run jq tests, keep using the ok and bad helpers for results, and fail fast if jq returns an error so spacing/formatting changes won't break the assertions..github/workflows/sign-attest.yml (1)
56-58: 💤 Low value
cancel-in-progress: falseplus a 5-platform matrix can deadlock if two tag pushes race.The concurrency group is keyed on
github.ref+inputs.version, with cancel disabled. If a second tag push (or re-dispatch) lands while the first run is mid-flight on the same ref/version, the second will queue indefinitely (until the first finishes or hits its 15-min timeout). That's the safer trade-off for signing — just confirm the operational expectation and consider documenting the queue behavior so reruns don't appear "stuck."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/sign-attest.yml around lines 56 - 58, The concurrency block (concurrency, group, cancel-in-progress) can cause queued runs to deadlock across the 5-platform matrix because group is keyed only on github.ref + inputs.version; either enable cancel-in-progress: true to allow a new run to cancel in-flight runs for the same group, or widen the group key (e.g., include github.run_id or matrix.platform) so concurrent tag pushes don't serialize all matrix jobs behind the first run; update the concurrency stanza accordingly and add a short comment documenting the chosen behavior so reruns aren't mistaken for stuck jobs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/release-publish.yml:
- Around line 68-86: The Resolve version (id: ver) and Resolve channel (id: ch)
steps currently interpolate ${{ inputs.version }} and ${{ inputs.channel }}
directly into the shell which allows script injection; change both steps to pass
the values via env (e.g., set env: INPUT_VERSION: ${{ inputs.version }} and env:
INPUT_CHANNEL: ${{ inputs.channel }}) and update the shell bodies to read from
the environment variables (e.g., use $INPUT_VERSION / $INPUT_CHANNEL) when
emitting to $GITHUB_OUTPUT, keeping the same fallback logic for empty values;
reference the steps by name "Resolve version" and "Resolve channel" and their
ids (ver, ch).
- Around line 139-162: The "Update latest.json channel pointer" step currently
uses git diff --quiet .well-known/latest.json which won't detect newly
created/untracked .well-known/latest.json; change the check to detect untracked
files by either staging first and using git diff --cached --quiet or comparing
against HEAD (git diff --quiet HEAD -- .well-known/latest.json), ensure you run
git add .well-known/latest.json before the diff if you choose --cached, and then
commit; additionally avoid pushing a detached tag HEAD by syncing to main first
(e.g., git fetch origin main && git checkout main && git pull) before committing
and use git push origin main (or use an action like
peter-evans/create-pull-request or stefanzweifel/git-auto-commit-action) instead
of git push origin HEAD:main to ensure the update lands on the main branch.
- Around line 88-101: Replace the two pattern-based download steps that use
patterns autopg-*-tarball and autopg-*-signed with a single
actions/download-artifact@v4 step that requests the aggregated artifact by name:
autopg-signed-bundle-${{ steps.ver.outputs.version }} and writes to path: dist/
(remove or stop using the pattern steps that expect autopg-${{
steps.ver.outputs.version }}-${{ matrix.platform }} or autopg-signed-${{
steps.ver.outputs.version }}-${{ matrix.platform }} artifacts); if this download
needs to pull artifacts from a different workflow run, also add the run-id and
github-token inputs to the download step so cross-workflow artifact access
succeeds.
In @.github/workflows/sign-attest.yml:
- Around line 87-97: The "Resolve version" step (id: ver) currently interpolates
`${{ inputs.version }}` directly into the shell which is a script-injection
sink; bind the input via an environment variable instead (add an env:
INPUT_VERSION: ${{ inputs.version }} to the step) and use the env var inside the
shell (e.g. read $INPUT_VERSION or fall back to package.json when empty) so the
shell body no longer contains direct GitHub Actions interpolation; apply the
same change to the aggregate job's resolve step (the resolve block at lines
~212-221) so both steps use env: INPUT_VERSION and reference the env variable in
their run bodies.
- Around line 153-164: The workflow step that runs cosign verify-blob references
keys/cosign.pub but that file is not in the repo, causing the verify and later
upload steps to fail; update the workflow so the cosign verification uses a
valid public key path or supplies one at runtime — either (A) add a step before
the cosign verify-blob step to populate keys/cosign.pub (e.g., copy
tests/fixtures/cosign/cosign.pub or fetch the production key into
keys/cosign.pub) or (B) change the key argument in the cosign verify-blob
invocation to point to the existing fixture file
(tests/fixtures/cosign/cosign.pub) when a production key isn’t available; ensure
the change affects the cosign verify-blob command that uses TARBALL (built from
steps.ver.outputs.version and matrix.platform) and likewise update the artifact
upload call that later references keys/cosign.pub.
In `@tests/integration/sign-attest-smoke.sh`:
- Around line 56-62: The cleanup function exits early using an arithmetic test
[[ "${AUTOPG_KEEP_DIST:-0}" -eq 1 ]] which fails for non-numeric values (e.g.
"yes") under set -euo pipefail; change the check in cleanup to a string test
against the truthy value(s) (for example use [[ "${AUTOPG_KEEP_DIST:-}" = "1" ]]
or [[ -n "${AUTOPG_KEEP_DIST:-}" && "${AUTOPG_KEEP_DIST:-}" != "0" ]] ) so any
non-empty/truthy AUTOPG_KEEP_DIST prevents removal of WORK_DIR without causing
an arithmetic error when trap cleanup EXIT runs.
---
Duplicate comments:
In @.github/workflows/build-tarballs.yml:
- Around line 140-143: The workflow uses an outdated runner label: update the
runner value for the darwin-x64 job (identify the block with platform:
darwin-x64 and runner: macos-13) to a current Intel-capable macOS hosted runner
such as macos-15-intel (or macos-26-intel) so the darwin-x64 build can be
scheduled and actionlint will pass; simply replace runner: macos-13 with runner:
macos-15-intel.
- Around line 201-210: The CI step titled "Verify size band (50–80MB)" is
inconsistent with the enforced bounds (30–120MB); update either the step title
or the bounds in the check so they match. Locate the job step using the title
string "Verify size band (50–80MB)" and the variables TARBALL and SIZE_MB, then
either change the title to reflect the actual sanity band (e.g., "Verify size
band (30–120MB)") or tighten the conditional range in the shell run block to
match 50–80 (adjust the numeric literals used in the if [[ "$SIZE_MB" -lt ... ||
"$SIZE_MB" -gt ... ]]; then check). Ensure the echoed error message and any
documentation strings are updated to the same bounds.
- Around line 158-199: The workflow interpolates untrusted inputs into run:
scripts (Resolve version and subsequent steps like "Build autopg binary",
"Assemble tarball", and "Smoke real tarball"), enabling script-injection via ${{
inputs.version }} and ${{ matrix.platform }}; capture the resolved value in the
Resolve version step outputs (steps.ver.outputs.version) and export it via env
on each later step (e.g., add env: { VERSION: ${{ steps.ver.outputs.version }},
PLATFORM: ${{ matrix.platform }} }) then change the run invocations to use the
env vars (call scripts/build-binary.sh, scripts/assemble-tarball.sh,
tests/integration/tarball-smoke.sh with --platform "$PLATFORM" --version
"$VERSION") so no user input is directly templated into run: bodies.
---
Nitpick comments:
In @.github/workflows/release-publish.yml:
- Around line 139-140: The current `if` condition on the "Update latest.json
channel pointer" step uses `if: ${{ steps.ch.outputs.channel == 'stable' &&
!inputs.draft }}` which treats undefined `inputs.draft` as true on tag-push
events; update the condition to explicitly require a non-draft release or that
the workflow was manually dispatched. Replace the guard with an explicit check
such as ensuring `inputs.draft != true` and/or `github.event_name ==
'workflow_dispatch'` (e.g., require both `steps.ch.outputs.channel == 'stable'
&& inputs.draft != true && github.event_name == 'workflow_dispatch'`) so
tag-push triggers don't unintentionally pass when `inputs.draft` is undefined.
- Around line 130-137: The release upload currently uses the glob "dist/*" with
gh release upload (and --clobber), which causes non-release files like build.log
and build-record.tsv (produced by build-tarballs.yml) to be attached and
overwritten across platforms; change the gh release upload invocation to only
upload explicit release asset patterns (e.g. tarball/zip/deb package globs or a
whitelist) or explicitly exclude build.log and build-record.tsv so only the
intended per-platform artifacts are uploaded, and keep --clobber semantics
aligned with the narrowed glob.
In @.github/workflows/sign-attest.yml:
- Around line 56-58: The concurrency block (concurrency, group,
cancel-in-progress) can cause queued runs to deadlock across the 5-platform
matrix because group is keyed only on github.ref + inputs.version; either enable
cancel-in-progress: true to allow a new run to cancel in-flight runs for the
same group, or widen the group key (e.g., include github.run_id or
matrix.platform) so concurrent tag pushes don't serialize all matrix jobs behind
the first run; update the concurrency stanza accordingly and add a short comment
documenting the chosen behavior so reruns aren't mistaken for stuck jobs.
In `@tests/fixtures/cosign/cosign.key`:
- Around line 1-11: Create a sibling README.md next to the cosign key fixture
(tests/fixtures/cosign/) that documents the encrypted Sigstore key in
cosign.key: state it is test-only/offline, include the fixed password
"autopg-fixture" (used by tests/integration/sign-attest-smoke.sh), reference the
matching public key file name (cosign.pub), and add a clear "DO NOT USE IN
PRODUCTION / DO NOT REUSE" warning; also mention the purpose of the key (offline
test signing) and suggest adding a scanner allowlist entry for this fixture if
your repo uses secret scanners.
In `@tests/integration/sign-attest-smoke.sh`:
- Around line 116-127: Replace the fragile grep-based JSON checks in the
sign-attest-smoke.sh test with structural jq queries: read
"${WORK_DIR}/manifest.json" and use jq to compare .version to the shell $VERSION
variable and to test that each element of the shell PLATFORMS array exists in
.platforms (or the appropriate array key) before calling ok/bad; update the
blocks that currently use grep -q "\"version\": \"${VERSION}\"" and grep -q
"\"platform\": \"${p}\"" to run jq tests, keep using the ok and bad helpers for
results, and fail fast if jq returns an error so spacing/formatting changes
won't break the assertions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b1ce7764-1d0d-4e31-95fa-4e5337ef80b3
⛔ Files ignored due to path filters (1)
tests/fixtures/cosign/cosign.pubis excluded by!**/*.pub
📒 Files selected for processing (9)
.github/workflows/build-tarballs.yml.github/workflows/release-publish.yml.github/workflows/sign-attest.ymlknip.jsonscripts/aggregate-manifest.shscripts/verify-published-artifacts.shtests/fixtures/cosign/README.mdtests/fixtures/cosign/cosign.keytests/integration/sign-attest-smoke.sh
✅ Files skipped from review due to trivial changes (4)
- tests/fixtures/cosign/README.md
- scripts/aggregate-manifest.sh
- scripts/verify-published-artifacts.sh
- knip.json
| - name: Resolve version | ||
| id: ver | ||
| run: | | ||
| if [ -n "${{ inputs.version }}" ]; then | ||
| echo "version=${{ inputs.version }}" >> "$GITHUB_OUTPUT" | ||
| else | ||
| # Strip leading `v` from tag ref | ||
| echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
||
| - name: Resolve channel | ||
| id: ch | ||
| run: | | ||
| if [ -n "${{ inputs.channel }}" ]; then | ||
| echo "channel=${{ inputs.channel }}" >> "$GITHUB_OUTPUT" | ||
| else | ||
| # Default tag pushes to stable; manual dispatches override above. | ||
| echo "channel=stable" >> "$GITHUB_OUTPUT" | ||
| fi |
There was a problem hiding this comment.
Same script-injection sink — bind inputs.version and inputs.channel via env:.
Both resolve steps interpolate ${{ inputs.* }} directly into shell. A malicious dispatch input would be substituted into the echo body. Use the same env: INPUT_VERSION / INPUT_CHANNEL pattern requested in build-tarballs.yml and sign-attest.yml.
🛡️ Proposed fix
- name: Resolve version
id: ver
+ env:
+ INPUT_VERSION: ${{ inputs.version }}
run: |
- if [ -n "${{ inputs.version }}" ]; then
- echo "version=${{ inputs.version }}" >> "$GITHUB_OUTPUT"
+ if [ -n "${INPUT_VERSION:-}" ]; then
+ echo "version=${INPUT_VERSION}" >> "$GITHUB_OUTPUT"
else
echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
fi
- name: Resolve channel
id: ch
+ env:
+ INPUT_CHANNEL: ${{ inputs.channel }}
run: |
- if [ -n "${{ inputs.channel }}" ]; then
- echo "channel=${{ inputs.channel }}" >> "$GITHUB_OUTPUT"
+ if [ -n "${INPUT_CHANNEL:-}" ]; then
+ echo "channel=${INPUT_CHANNEL}" >> "$GITHUB_OUTPUT"
else
echo "channel=stable" >> "$GITHUB_OUTPUT"
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.
| - name: Resolve version | |
| id: ver | |
| run: | | |
| if [ -n "${{ inputs.version }}" ]; then | |
| echo "version=${{ inputs.version }}" >> "$GITHUB_OUTPUT" | |
| else | |
| # Strip leading `v` from tag ref | |
| echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Resolve channel | |
| id: ch | |
| run: | | |
| if [ -n "${{ inputs.channel }}" ]; then | |
| echo "channel=${{ inputs.channel }}" >> "$GITHUB_OUTPUT" | |
| else | |
| # Default tag pushes to stable; manual dispatches override above. | |
| echo "channel=stable" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Resolve version | |
| id: ver | |
| env: | |
| INPUT_VERSION: ${{ inputs.version }} | |
| run: | | |
| if [ -n "${INPUT_VERSION:-}" ]; then | |
| echo "version=${INPUT_VERSION}" >> "$GITHUB_OUTPUT" | |
| else | |
| # Strip leading `v` from tag ref | |
| echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Resolve channel | |
| id: ch | |
| env: | |
| INPUT_CHANNEL: ${{ inputs.channel }} | |
| run: | | |
| if [ -n "${INPUT_CHANNEL:-}" ]; then | |
| echo "channel=${INPUT_CHANNEL}" >> "$GITHUB_OUTPUT" | |
| else | |
| # Default tag pushes to stable; manual dispatches override above. | |
| echo "channel=stable" >> "$GITHUB_OUTPUT" | |
| fi |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/release-publish.yml around lines 68 - 86, The Resolve
version (id: ver) and Resolve channel (id: ch) steps currently interpolate ${{
inputs.version }} and ${{ inputs.channel }} directly into the shell which allows
script injection; change both steps to pass the values via env (e.g., set env:
INPUT_VERSION: ${{ inputs.version }} and env: INPUT_CHANNEL: ${{ inputs.channel
}}) and update the shell bodies to read from the environment variables (e.g.,
use $INPUT_VERSION / $INPUT_CHANNEL) when emitting to $GITHUB_OUTPUT, keeping
the same fallback logic for empty values; reference the steps by name "Resolve
version" and "Resolve channel" and their ids (ver, ch).
| - name: Download tarball artifacts (build-tarballs.yml) | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| pattern: autopg-*-tarball | ||
| merge-multiple: true | ||
| path: dist/ | ||
|
|
||
| - name: Download signature artifacts (sign-attest.yml) | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| pattern: autopg-*-signed | ||
| merge-multiple: true | ||
| path: dist/ | ||
| continue-on-error: true # sign-attest.yml may not have run on dispatch |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm upload names so the download pattern can be aligned exactly.
rg -nP --type=yaml -C2 '^\s*name:\s*autopg-' .github/workflows/build-tarballs.yml .github/workflows/sign-attest.yml
echo "---"
rg -nP --type=yaml -C2 'pattern:\s*autopg' .github/workflows/release-publish.ymlRepository: namastexlabs/pgserve
Length of output: 2097
Critical: artifact download patterns don't match the names produced upstream — dist/ will be empty.
build-tarballs.ymlline 215 uploads withname: autopg-${{ steps.ver.outputs.version }}-${{ matrix.platform }}(e.g.autopg-2.5.0-linux-x64-glibc), so patternautopg-*-tarballmatches nothing.sign-attest.ymlline 192 uploads withname: autopg-signed-${{ steps.ver.outputs.version }}-${{ matrix.platform }}(e.g.autopg-signed-2.5.0-linux-x64-glibc), so patternautopg-*-signedmatches nothing.
Together with continue-on-error: true on the second download, the release will silently publish with zero assets. Either align the patterns to the actual artifact names, or download the aggregated autopg-signed-bundle-${{ steps.ver.outputs.version }} artifact emitted by sign-attest.yml line 276 (preferred — single artifact, already verified).
🛠️ Proposed fix (option A: align patterns)
- name: Download tarball artifacts (build-tarballs.yml)
uses: actions/download-artifact@v4
with:
- pattern: autopg-*-tarball
+ pattern: autopg-${{ steps.ver.outputs.version }}-*
merge-multiple: true
path: dist/
- name: Download signature artifacts (sign-attest.yml)
uses: actions/download-artifact@v4
with:
- pattern: autopg-*-signed
+ pattern: autopg-signed-${{ steps.ver.outputs.version }}-*
merge-multiple: true
path: dist/
continue-on-error: trueOption B (cleaner): download the aggregated bundle from sign-attest.yml in one shot:
- name: Download aggregated signed bundle
uses: actions/download-artifact@v4
with:
name: autopg-signed-bundle-${{ steps.ver.outputs.version }}
path: dist/Note: cross-workflow downloads still need run-id + github-token. Without run-id, this action only sees artifacts from the current workflow run, so neither option will work as-is on a push: tags trigger. Consider triggering release-publish from workflow_run of sign-attest, or thread a run_id input through.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/release-publish.yml around lines 88 - 101, Replace the two
pattern-based download steps that use patterns autopg-*-tarball and
autopg-*-signed with a single actions/download-artifact@v4 step that requests
the aggregated artifact by name: autopg-signed-bundle-${{
steps.ver.outputs.version }} and writes to path: dist/ (remove or stop using the
pattern steps that expect autopg-${{ steps.ver.outputs.version }}-${{
matrix.platform }} or autopg-signed-${{ steps.ver.outputs.version }}-${{
matrix.platform }} artifacts); if this download needs to pull artifacts from a
different workflow run, also add the run-id and github-token inputs to the
download step so cross-workflow artifact access succeeds.
| - name: Update latest.json channel pointer | ||
| if: ${{ steps.ch.outputs.channel == 'stable' && !inputs.draft }} | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| VERSION: ${{ steps.ver.outputs.version }} | ||
| run: | | ||
| mkdir -p .well-known | ||
| cat > .well-known/latest.json <<EOF | ||
| { | ||
| "channel": "stable", | ||
| "version": "${VERSION}", | ||
| "released_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", | ||
| "tarball_base": "https://github.com/${{ github.repository }}/releases/download/v${VERSION}", | ||
| "platforms": ["linux-x64-glibc","linux-x64-musl","linux-arm64","darwin-x64","darwin-arm64"] | ||
| } | ||
| EOF | ||
| # Commit only if changed; CI bot configured globally | ||
| if ! git diff --quiet .well-known/latest.json; then | ||
| git config user.name "release-bot" | ||
| git config user.email "release-bot@namastex.com" | ||
| git add .well-known/latest.json | ||
| git commit -m "chore(release): update latest.json → v${VERSION}" | ||
| git push origin HEAD:main || echo "::warning::push failed (likely permission); update latest.json manually" | ||
| fi |
There was a problem hiding this comment.
Critical: git diff --quiet won't detect a newly created latest.json — first publish silently skips the commit.
git diff --quiet PATH only inspects modifications to files already tracked by git. On the first run (or any run where .well-known/latest.json has just been created/recreated and isn't in HEAD yet), the diff returns 0 and the entire git add/commit/push block is skipped — the channel pointer never lands. Compare against HEAD (or stage first and use --cached) so untracked/new files are detected.
Also, git push origin HEAD:main from a tag-push run is pushing the tag's detached HEAD onto main — that will replay any commits that exist on the tag but not on main, which is rarely what you want. Consider git fetch origin main && git checkout main && git pull (or use peter-evans/create-pull-request / a dedicated commit action) before staging the file.
🛠️ Proposed fix (minimum viable)
- # Commit only if changed; CI bot configured globally
- if ! git diff --quiet .well-known/latest.json; then
- git config user.name "release-bot"
- git config user.email "release-bot@namastex.com"
- git add .well-known/latest.json
- git commit -m "chore(release): update latest.json → v${VERSION}"
- git push origin HEAD:main || echo "::warning::push failed (likely permission); update latest.json manually"
- fi
+ git config user.name "release-bot"
+ git config user.email "release-bot@namastex.com"
+ git add .well-known/latest.json
+ # `--cached` works for both new and modified files
+ if ! git diff --cached --quiet -- .well-known/latest.json; then
+ git commit -m "chore(release): update latest.json → v${VERSION}"
+ # Don't push tag HEAD onto main — fetch/check out main first.
+ git fetch origin main
+ git checkout -B main origin/main -- || true
+ git cherry-pick --allow-empty HEAD@{1} 2>/dev/null || true
+ git push origin HEAD:main \
+ || echo "::warning::push failed; update latest.json manually"
+ fi(Or replace the manual git block with stefanzweifel/git-auto-commit-action.)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/release-publish.yml around lines 139 - 162, The "Update
latest.json channel pointer" step currently uses git diff --quiet
.well-known/latest.json which won't detect newly created/untracked
.well-known/latest.json; change the check to detect untracked files by either
staging first and using git diff --cached --quiet or comparing against HEAD (git
diff --quiet HEAD -- .well-known/latest.json), ensure you run git add
.well-known/latest.json before the diff if you choose --cached, and then commit;
additionally avoid pushing a detached tag HEAD by syncing to main first (e.g.,
git fetch origin main && git checkout main && git pull) before committing and
use git push origin main (or use an action like peter-evans/create-pull-request
or stefanzweifel/git-auto-commit-action) instead of git push origin HEAD:main to
ensure the update lands on the main branch.
| - name: Resolve version | ||
| id: ver | ||
| shell: bash | ||
| run: | | ||
| if [[ -n "${{ inputs.version }}" ]]; then | ||
| VERSION="${{ inputs.version }}" | ||
| else | ||
| VERSION=$(node -p "require('./package.json').version") | ||
| fi | ||
| echo "version=${VERSION}" >> "$GITHUB_OUTPUT" | ||
| echo "Resolved version: ${VERSION}" |
There was a problem hiding this comment.
Same script-injection sink — bind ${{ inputs.version }} via env: here and in the aggregate job's resolve step (lines 212-221).
inputs.version is interpolated directly into both resolve steps' shell bodies. Apply the same env: INPUT_VERSION binding pattern requested for build-tarballs.yml to keep the surface lint-clean and consistent.
🛡️ Proposed fix (apply to both resolve steps)
- name: Resolve version
id: ver
shell: bash
+ env:
+ INPUT_VERSION: ${{ inputs.version }}
run: |
- if [[ -n "${{ inputs.version }}" ]]; then
- VERSION="${{ inputs.version }}"
+ if [[ -n "${INPUT_VERSION:-}" ]]; then
+ VERSION="${INPUT_VERSION}"
else
VERSION=$(node -p "require('./package.json').version")
fi
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/sign-attest.yml around lines 87 - 97, The "Resolve
version" step (id: ver) currently interpolates `${{ inputs.version }}` directly
into the shell which is a script-injection sink; bind the input via an
environment variable instead (add an env: INPUT_VERSION: ${{ inputs.version }}
to the step) and use the env var inside the shell (e.g. read $INPUT_VERSION or
fall back to package.json when empty) so the shell body no longer contains
direct GitHub Actions interpolation; apply the same change to the aggregate
job's resolve step (the resolve block at lines ~212-221) so both steps use env:
INPUT_VERSION and reference the env variable in their run bodies.
| - name: cosign verify-blob (self-check) | ||
| shell: bash | ||
| env: | ||
| VERSION: ${{ steps.ver.outputs.version }} | ||
| PLATFORM: ${{ matrix.platform }} | ||
| run: | | ||
| set -euo pipefail | ||
| TARBALL="dist/autopg-${VERSION}-${PLATFORM}.tar.gz" | ||
| cosign verify-blob \ | ||
| --key keys/cosign.pub \ | ||
| --signature "${TARBALL}.sig" \ | ||
| "${TARBALL}" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm presence (or absence) of the production public key referenced by sign-attest.yml.
fd -uH cosign.pub
echo "---"
ls -la keys/ 2>/dev/null || echo "no keys/ dir"
echo "---"
# Cross-check workflow references
rg -nP --type=yaml -C2 'keys/cosign\.pub'Repository: namastexlabs/pgserve
Length of output: 118
🏁 Script executed:
# Get the full sign-attest.yml file to check line 283
wc -l .github/workflows/sign-attest.yml
echo "---"
# Show lines around 283
sed -n '280,286p' .github/workflows/sign-attest.ymlRepository: namastexlabs/pgserve
Length of output: 378
Missing production public key will cause workflow failures — keys/cosign.pub is referenced but not present in the repository.
Lines 162 and 283 reference keys/cosign.pub for signature verification (self-check) and artifact upload, respectively. The repository contains only the fixture keypair at tests/fixtures/cosign/cosign.pub. Without the production public key at keys/cosign.pub, the verify-blob step will fail and the artifact upload will error on if-no-files-found: error.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/sign-attest.yml around lines 153 - 164, The workflow step
that runs cosign verify-blob references keys/cosign.pub but that file is not in
the repo, causing the verify and later upload steps to fail; update the workflow
so the cosign verification uses a valid public key path or supplies one at
runtime — either (A) add a step before the cosign verify-blob step to populate
keys/cosign.pub (e.g., copy tests/fixtures/cosign/cosign.pub or fetch the
production key into keys/cosign.pub) or (B) change the key argument in the
cosign verify-blob invocation to point to the existing fixture file
(tests/fixtures/cosign/cosign.pub) when a production key isn’t available; ensure
the change affects the cosign verify-blob command that uses TARBALL (built from
steps.ver.outputs.version and matrix.platform) and likewise update the artifact
upload call that later references keys/cosign.pub.
| cleanup() { | ||
| if [[ "${AUTOPG_KEEP_DIST:-0}" -eq 1 ]]; then | ||
| note "AUTOPG_KEEP_DIST=1 — keeping ${WORK_DIR}" | ||
| return | ||
| fi | ||
| rm -rf "${WORK_DIR}" | ||
| } |
There was a problem hiding this comment.
-eq will abort cleanup if AUTOPG_KEEP_DIST is set to a non-numeric value.
[[ "${AUTOPG_KEEP_DIST:-0}" -eq 1 ]] performs arithmetic evaluation; a value like yes/true raises value too great for base / not a valid number and, because this runs from a trap cleanup EXIT under set -euo pipefail, the script exits with a confusing error instead of cleaning up. Use a string comparison so any "truthy-ish" value works.
🛡️ Proposed fix
cleanup() {
- if [[ "${AUTOPG_KEEP_DIST:-0}" -eq 1 ]]; then
+ if [[ "${AUTOPG_KEEP_DIST:-0}" == "1" ]]; then
note "AUTOPG_KEEP_DIST=1 — keeping ${WORK_DIR}"
return
fi
rm -rf "${WORK_DIR}"
}📝 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.
| cleanup() { | |
| if [[ "${AUTOPG_KEEP_DIST:-0}" -eq 1 ]]; then | |
| note "AUTOPG_KEEP_DIST=1 — keeping ${WORK_DIR}" | |
| return | |
| fi | |
| rm -rf "${WORK_DIR}" | |
| } | |
| cleanup() { | |
| if [[ "${AUTOPG_KEEP_DIST:-0}" == "1" ]]; then | |
| note "AUTOPG_KEEP_DIST=1 — keeping ${WORK_DIR}" | |
| return | |
| fi | |
| rm -rf "${WORK_DIR}" | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/integration/sign-attest-smoke.sh` around lines 56 - 62, The cleanup
function exits early using an arithmetic test [[ "${AUTOPG_KEEP_DIST:-0}" -eq 1
]] which fails for non-numeric values (e.g. "yes") under set -euo pipefail;
change the check in cleanup to a string test against the truthy value(s) (for
example use [[ "${AUTOPG_KEEP_DIST:-}" = "1" ]] or [[ -n "${AUTOPG_KEEP_DIST:-}"
&& "${AUTOPG_KEEP_DIST:-}" != "0" ]] ) so any non-empty/truthy AUTOPG_KEEP_DIST
prevents removal of WORK_DIR without causing an arithmetic error when trap
cleanup EXIT runs.
…N trap unbound var Closes the worth-fixing bot-review findings on PR #84: [HIGH × 5] set -e suppressed inside assemble_one / build_one / fetch_one because main calls them via 'fn ... || rc=$?'. Failures of mkdir / tar / verify_inputs / emit_manifest / stage_from_local|pkg|url silently propagate as success, producing corrupt or partial tarballs. Fix: explicit '|| return 1' after each potentially-failing command in: - scripts/assemble-tarball.sh: verify_inputs, emit_manifest, tar, sha256_of - scripts/build-binary.sh: mkdir -p - scripts/fetch-postgres-bins.sh: rm -rf, mkdir -p, stage_from_local, stage_from_pkg, stage_from_url [MEDIUM] RETURN trap on local 'scratch' under set -u prints 'scratch: unbound variable' on early-return paths (e.g. stage_from_pkg when AUTOPG_POSTGRES_PKG_VERSION is invalid), masking the original error. Fix: initialize 'local scratch=""' before installing the trap. Filtered noise (intentionally NOT addressed): - shellcheck-references-missing-scripts: already fixed in earlier commit; bots haven't re-scanned the latest PR head. - JSON escaping in $f: no realistic special chars in binary filenames. - macos-13 deprecation: cosmetic until GH actually removes the runner. Local validation: - shellcheck -S warning on all 3 scripts: clean - bash tests/integration/sign-attest-smoke.sh: 15 pass / 0 fail Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
v2.6.4 published to npm but the GH Releases pipeline failed at build-tarballs because scripts/fetch-postgres-bins.sh's stage_from_url declared 'local scratch' without initialization. Under 'set -u' the RETURN trap fired with an unbound $scratch, masking the real fetch state and exiting 1 across all four platform builds. Codex P2 review on PR #84 caught the same bug in stage_from_pkg (line 119) but stage_from_url was missed at that time. This commit applies the same fix to stage_from_url. v2.6.5 is purely the GH Releases completion — npm consumers on pgserve: ^2.x will see v2.6.5 instead of v2.6.4 on next install but the runtime surface is identical. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cutover wish G7 + G8 surgical extractions, with G9 pivoted to GitHub Releases per Felipe's directive (2026-05-08): drop
cdn.automagik.devinfrastructure, use the free GitHub Releases pipeline that already integrates with Sigstore Rekor.Distribution model — GitHub Releases
cdn.automagik.dev/autopg/<channel>/<v>/<plat>/github.com/namastexlabs/pgserve/releases/download/v<v>/cosign verify-blobagainst repo pub keygh attestation verify(built-in, reads Sigstore Rekor)latest.jsonw/ cache gymnastics.well-known/latest.jsoncommitted to repoWhat ships
G7 — bun-build static binaries (5 platforms)
scripts/build-binary.sh—bun build --compilefor 5 platformsscripts/fetch-postgres-bins.sh— stages PG binariesscripts/assemble-tarball.sh— packs per-platform release tarballtests/integration/tarball-smoke.sh— fixture.github/workflows/build-tarballs.yml— CI matrixG8 — cosign keyless OIDC sign + SLSA L3 attestation
.github/workflows/sign-attest.yml— per-platform cosign sign-blob +actions/attest-build-provenance@v1tests/integration/sign-attest-smoke.sh— hermetic smoke fixture (no real Sigstore)G9 — Replaced with GitHub Releases pipeline (~30 LOC vs 191)
.github/workflows/release-publish.yml—gh release create+gh release upload, draft/prerelease channel support, commits.well-known/latest.jsonforinstall.shconsumersCI red — fix in this PR
Initial commit referenced 3 CDN-specific scripts (
aggregate-manifest.sh,verify-published-artifacts.sh,scripts/cdn-publish.sh) that were never copied. The pivot makes those references unnecessary; CI should now pass.Validation
bash -non all 4 scripts → syntax cleanbun run lint→ cleanbun run lint:audit→ 32 files scanned, 0 issuesbun run deadcode→ clean (addedghtoignoreBinaries)Cohort
After this lands:
autopg create-app+ manifest LOCK 1 (structural entanglement; needs proper engineering effort)install.sh, when revived, fetches fromgithub.com/.../releases/download/.... Collides with main's existing 123-line legacy installer; needs rename decision.Provisioning prerequisites
None for GitHub Releases (already provisioned by GitHub). The cosign workflow uses keyless OIDC — no key management required.
Summary by CodeRabbit
New Features
Tests