feat: hash-based manual release workflow#20
Conversation
Mirror the manual build/release pipeline from e2b-dev/fc-versions, but identify each kernel build by a content hash of its inputs (configs + optional patches). Changing a flag or adding a patch yields a new artifact named vmlinux-<version>_<sha256[:7]>. - scripts/validate.py: resolves version names, computes hashes, builds the matrix and skips arches whose artifact is already in the GitHub release. - build.sh: now accepts <kernel_version> [arch], computes the same version_name (or honors VERSION_NAME), supports patches/<version>/. - .github/workflows/release.yml: workflow_dispatch with validate -> build -> publish (GH release) -> deploy (GCS), skipping work whose artifacts already exist. - Drops the old build-on-every-push workflow; releases are explicit.
PR SummaryMedium Risk Overview
The PR title/description mention hash-based versioning and skipping already-published artifacts, but the included workflow/script changes shown here don’t implement that (no content-hash naming or reuse/skip logic), so releases will always rebuild and tag by date. Reviewed by Cursor Bugbot for commit fd2430a. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Artifact path loses version_name directory on download
- Changed artifact upload path from ./builds/$version_name to ./builds to preserve the version_name directory structure in artifacts.
- ✅ Fixed: Multi-version local builds break when patches are applied
- Added -f flag to git checkout to force-overwrite uncommitted patch changes between multi-version build iterations.
Or push these changes by commenting:
@cursor push a85f2957d0
Preview (a85f2957d0)
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -67,7 +67,7 @@
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.version_name }}-${{ matrix.arch }}
- path: ./builds/${{ matrix.version_name }}
+ path: ./builds
retention-days: 7
publish:
diff --git a/build.sh b/build.sh
--- a/build.sh
+++ b/build.sh
@@ -102,7 +102,7 @@
cp "$SCRIPT_DIR/configs/${target_arch}/${version}.config" .config
echo "Checking out repo for kernel at version: $version"
- git checkout "$(get_tag "$version")"
+ git checkout -f "$(get_tag "$version")"
apply_patches "$version"You can send follow-ups to the cloud agent here.
- upload-artifact: keep version_name in the artifact tree so the publish step's ./builds/<version_name>/<arch>/vmlinux.bin lookup resolves after merge-multiple download. - build.sh: git checkout -f so a dirty tree from a prior iteration's apply_patches doesn't abort multi-version local builds.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Hash includes nested patches that apply_patches never applies
- Modified hash computation in both build.sh and validate.py to only include top-level *.patch files, matching the behavior of apply_patches.
Or push these changes by commenting:
@cursor push 3e7936a79e
Preview (3e7936a79e)
diff --git a/build.sh b/build.sh
--- a/build.sh
+++ b/build.sh
@@ -37,7 +37,7 @@
if [ -d "$SCRIPT_DIR/patches/$version" ]; then
while IFS= read -r -d '' f; do
files+=("$f")
- done < <(find "$SCRIPT_DIR/patches/$version" -type f -print0 | sort -z)
+ done < <(find "$SCRIPT_DIR/patches/$version" -maxdepth 1 -type f -name '*.patch' -print0 | sort -z)
fi
if [ "${#files[@]}" -eq 0 ]; then
echo "Error: no configs found for kernel version $version" >&2
diff --git a/scripts/validate.py b/scripts/validate.py
--- a/scripts/validate.py
+++ b/scripts/validate.py
@@ -51,7 +51,7 @@
paths.append(cfg)
patches_dir = REPO_ROOT / "patches" / version
if patches_dir.is_dir():
- paths.extend(sorted(p for p in patches_dir.rglob("*") if p.is_file()))
+ paths.extend(sorted(p for p in patches_dir.glob("*.patch") if p.is_file()))
if not paths:
raise SystemExit(f"::error::No configs found for kernel version {version}")You can send follow-ups to the cloud agent here.
apply_patches only processes patches/<v>/*.patch, so the hash inputs must match. Otherwise unrelated/nested files in patches/<v>/ would silently change the version_name without affecting the build.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Inconsistent kernel_versions.txt parsing between Python and Bash
- Updated build.sh to strip inline comments and trim whitespace, matching the Python parser's behavior to ensure consistent version hashing.
Or push these changes by commenting:
@cursor push c23d53f5a8
Preview (c23d53f5a8)
diff --git a/build.sh b/build.sh
--- a/build.sh
+++ b/build.sh
@@ -161,7 +161,9 @@
local version_name="${VERSION_NAME:-$(compute_version_name "$single_version")}"
build_version "$single_version" "$target_arch" "$version_name"
else
- grep -v '^ *#' <"$SCRIPT_DIR/kernel_versions.txt" | while IFS= read -r version; do
+ grep -v '^ *#' <"$SCRIPT_DIR/kernel_versions.txt" | while IFS= read -r raw; do
+ version="${raw%%#*}"
+ version="$(echo "$version" | xargs)"
[ -z "$version" ] && continue
local version_name
version_name="$(compute_version_name "$version")"You can send follow-ups to the cloud agent here.
Strip inline comments and trim whitespace so build.sh and scripts/validate.py produce identical version names for entries like `6.1.158 # stable`.
- workflow_dispatch with no inputs; pick the branch in the GitHub UI. - Always build every entry in kernel_versions.txt for amd64 and arm64 in parallel (one runner per arch). - One release per run, tagged YYYY.MM.DD (with .N suffix on collision), with every binary uploaded as vmlinux-<version>-<arch>.bin (plus the legacy vmlinux-<version>.bin for amd64). - Same binaries pushed to GCS under kernels/vmlinux-<version>/... Drops the per-build content-hash version_name machinery and the validate.py helper that fed it.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Calver tag suffix skips
.1due to off-by-one- Changed counter initialization from n=1 to n=0 so the first suffixed tag is .1 instead of .2, matching the documented behavior.
Or push these changes by commenting:
@cursor push 73766c5030
Preview (73766c5030)
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -67,7 +67,7 @@
set -euo pipefail
base="$(date -u +%Y.%m.%d)"
tag="$base"
- n=1
+ n=0
while gh release view "$tag" >/dev/null 2>&1; do
n=$((n + 1))
tag="${base}.${n}"You can send follow-ups to the cloud agent here.
Open PRs now run the build matrix and upload kernel binaries as workflow artifacts so reviewers can grab and inspect them from the PR's checks tab. Release/GCS publishing remains manual-only.
Generate the build matrix dynamically from kernel_versions.txt so each (version, arch) pair runs on its own runner. With N versions × 2 archs that's 2N parallel jobs (e.g. 2 versions -> 4 runners). build.sh already supports single-version mode, so no script changes; the matrix job emits a JSON include list that the build job fans out over.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Calver tag check ignores existing git tags causing retry failures
- Changed tag uniqueness check from 'gh release view' to 'git rev-parse' to detect existing git tags, making the workflow idempotent on partial failures.
Or push these changes by commenting:
@cursor push 4a50f9f36e
Preview (4a50f9f36e)
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -86,7 +86,7 @@
base="$(date -u +%Y.%m.%d)"
tag="$base"
n=0
- while gh release view "$tag" >/dev/null 2>&1; do
+ while git rev-parse "$tag" >/dev/null 2>&1; do
n=$((n + 1))
tag="${base}.${n}"
doneYou can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit c167397. Configure here.
…alver - Drop workflow-level id-token: write; only the publish job (which needs OIDC for GCP auth) gets id-token: write. PR builds no longer receive a token they can exchange for cloud creds. - Validate kernel versions against [0-9]+(\.[0-9]+)+ in the matrix job and pass matrix.version to build.sh via env vars instead of inline YAML expression interpolation, so a malicious kernel_versions.txt entry can't shell-inject into the runner. - Calver tag picker now also checks local and remote git tags, not just GH releases, so retries after a partial publish no longer pick a tag that's already been pushed.
The 'Main' ruleset on this repo requires status checks named exactly 'Build kernels (x86_64)' and 'Build kernels (arm64)' (left over from the previous workflow). The new per-(version, arch) jobs use different names, so those required checks never reported and PRs couldn't merge. Add a tiny aggregator job whose name interpolates to those exact two strings and that gates on the build matrix's combined result, so parallelism stays per-(version, arch) but branch protection is satisfied.


Mirrors the manual release pipeline from e2b-dev/fc-versions, but because the kernel inputs (configs + patches) live in this repo, each build is identified by a content hash of those inputs:
Changing a flag in
configs/x86_64/<v>.configor dropping a patch inpatches/<v>/produces a new, traceable artifact and release; unchanged kernels reuse their existing artifact.What's in here
.github/workflows/release.yml—workflow_dispatchpipeline:validate→build(matrix per missing arch) →publish(GH release withvmlinux-{amd64,arm64}.bin+ legacyvmlinux.bin) →deploy(uploads togs://$GCP_BUCKET_NAME/kernels/<version_name>/). Existing artifacts/objects are skipped.scripts/validate.py— resolves version names, computes hashes, generates the build matrix, and queries the GitHub release to skip arches that are already published.build.sh— now takes<kernel_version> [arch], computes the sameversion_name(or honorsVERSION_NAME), and applies anypatches/<version>/*.patch.make build/make build-arm64still work.Workflow inputs
kernel_versions(optional, comma-separated) — defaults tokernel_versions.txt.build_amd64/build_arm64(default true) — arch toggles.Notes
GCP_WORKLOAD_IDENTITY_PROVIDER,GCP_SERVICE_ACCOUNT_EMAIL,vars.GCP_BUCKET_NAME.6.1.102and6.1.158configs.