Fix four reproducible bugs in the release pipeline#89
Conversation
…expects go build -o writes the output name verbatim — it never appends .exe — so the goos=windows path always failed at `mv <name>.exe`. Build with the same suffix the Package step moves. darwin/linux commands are unchanged, and no current caller builds windows, so nothing live changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…counter works The github-release job picks release/<date>.N by counting existing tags, but its shallow checkout fetched no tags, so N was always 0 and a second same-day release reused release/<date>.0 — action-gh-release then updates the existing release instead of creating a new one. bump-versions and npm-publish in this workflow already check out with fetch-depth: 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eviewer requests The extraction regex matched the bare @org prefix of @org/team entries (the comment says "not @org/team" but the pattern stops at the slash). GitHub's request-reviewers call is all-or-nothing — one invalid login rejects the whole request — and the gh call runs under `|| true`, so a single team entry silently disabled reviewer assignment entirely. Match the full token and drop anything with a slash; logins cannot contain '/', so every dropped entry was already guaranteed-invalid. Also wrap the grep in `|| true`: these steps run with pipefail, and a CODEOWNERS with no @entries made grep exit 1 and fail the job. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…onment Ports 81a89f4 (the update-docs fix) to the identical code here: export DIFF put the untruncated git diff into the environment, and a single env var over ~128 KiB makes execve() fail with E2BIG ("Argument list too long"), so any release whose diff exceeded that killed gather-changes before the Python truncation ever ran. The diff now goes through ${RUNNER_TEMP}/changes.diff exactly like update-docs.yaml. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis pull request makes four targeted improvements to GitHub Actions workflows and composite actions. The Go build action gains Windows-aware artifact naming, the release workflow fixes tag enumeration via full repository fetch, reviewer extraction is refined in two workflows to exclude team identifiers, and release diff generation is moved to file-based handling to avoid environment size constraints. ChangesCI/CD Infrastructure Improvements
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
📄 README may need an updateThis PR introduces changes that might not be reflected in Reason:
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/typescript-monorepo-release.yaml:
- Around line 154-158: In the github-release job update the actions/checkout
step (the one that currently sets fetch-depth: 0) to add persist-credentials:
false so credentials are not retained for that checkout; locate the checkout
step inside the github-release job and add the persist-credentials: false key
alongside fetch-depth: 0 to limit token exposure (this affects the checkout
action used prior to git tag -l and softprops/action-gh-release).
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ef7813ad-9fbe-47b2-b4ff-f996f60ecb0e
📒 Files selected for processing (4)
.github/blocks/go-build/action.yaml.github/workflows/typescript-monorepo-release.yaml.github/workflows/update-docs.yaml.github/workflows/update-skills.yaml
📜 Review details
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2026-04-05T00:07:12.194Z
Learnt from: qwerzl
Repo: photon-hq/buildspace PR: 62
File: .github/workflows/update-docs.yaml:164-167
Timestamp: 2026-04-05T00:07:12.194Z
Learning: In this repo (photon-hq/buildspace), reusable workflow blocks referenced under photon-hq/buildspace (e.g., `photon-hq/buildspace/.github/blocks/<block>main`) are intentionally pinned to `main` because the blocks and workflows ship together and are kept in sync. During review, do not flag these references as version-pinning/supply-chain issues just because they use `main`; only require pinning to a release tag if there’s a separate reason (e.g., referencing an external repo or a non-controlled branch).
Applied to files:
.github/workflows/typescript-monorepo-release.yaml.github/workflows/update-skills.yaml.github/workflows/update-docs.yaml
📚 Learning: 2026-04-27T01:30:22.893Z
Learnt from: yanxue06
Repo: photon-hq/buildspace PR: 73
File: .github/workflows/check-readme.yaml:18-18
Timestamp: 2026-04-27T01:30:22.893Z
Learning: When reviewing this repo’s GitHub Actions workflows, treat Blacksmith runner labels like `blacksmith-4vcpu-ubuntu-2404` and other `blacksmith-*vcpu-ubuntu-*` values as valid/intentional third-party runner labels (Blacksmith: blacksmith.sh). Do not flag them as unknown or non-standard runner labels—these are an intentional drop-in replacement for GitHub-hosted runners.
Applied to files:
.github/workflows/typescript-monorepo-release.yaml.github/workflows/update-skills.yaml.github/workflows/update-docs.yaml
📚 Learning: 2026-04-27T01:30:22.893Z
Learnt from: yanxue06
Repo: photon-hq/buildspace PR: 73
File: .github/workflows/check-readme.yaml:18-18
Timestamp: 2026-04-27T01:30:22.893Z
Learning: In photon-hq/buildspace GitHub workflow YAML files, runner labels that match `blacksmith-*vcpu-ubuntu-*` (e.g., `blacksmith-4vcpu-ubuntu-2404`) are intentionally managed by the Blacksmith CI service (blacksmith.sh) and serve as drop-in replacements for GitHub-hosted ephemeral microVM runners. Do not flag these labels as unknown/non-standard, supply-chain concerns, or “unrecognized runner” issues; any linter/actionlint warnings about these specific labels are expected false positives.
Applied to files:
.github/workflows/typescript-monorepo-release.yaml.github/workflows/update-skills.yaml.github/workflows/update-docs.yaml
🪛 zizmor (1.25.2)
.github/workflows/typescript-monorepo-release.yaml
[warning] 152-158: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
[error] 153-153: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🔇 Additional comments (3)
.github/blocks/go-build/action.yaml (1)
50-56: LGTM!.github/workflows/update-docs.yaml (1)
99-104: LGTM!.github/workflows/update-skills.yaml (1)
132-137: LGTM!Also applies to: 182-182, 185-185, 189-190, 197-203
| with: | ||
| # The release/<date>.N counter below counts existing tags; the default | ||
| # shallow checkout fetches no tags, so N was always 0 and a second | ||
| # same-day release reused (and overwrote) the first one's tag. | ||
| fetch-depth: 0 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify whether github-release job actually needs persisted checkout credentials.
# Expected: no `git push`/authenticated write commands in this job, making persist-credentials unnecessary.
awk '
/github-release:/ {in_job=1}
in_job && /^[^[:space:]]/ && $0 !~ /^github-release:/ {in_job=0}
in_job {print NR ":" $0}
' .github/workflows/typescript-monorepo-release.yaml | rg -n 'git push|gh release|gh api|persist-credentials'Repository: photon-hq/buildspace
Length of output: 46
🏁 Script executed:
#!/bin/bash
set -euo pipefail
WF=".github/workflows/typescript-monorepo-release.yaml"
# Extract the github-release job block (from "github-release:" up to the next top-level job key)
awk '
/^ [A-Za-z0-9_-]+:/ { # top-level keys under "jobs:"
if ($1 ~ /^[A-Za-z0-9_-]+:$/) {
job=$1
}
}
/^ github-release:/ {in=1; print; next}
in && /^ [A-Za-z0-9_-]+:/ {in=0}
in {print}
' "$WF" > /tmp/github-release-job.yaml
# Print the extracted block with line numbers but keep it concise
nl -ba /tmp/github-release-job.yaml | sed -n '1,220p'
# Search within the job for any signs that persisted git creds might be needed
rg -n --hidden --no-ignore -S \
'git push|git tag|git commit|git reflog|gh release|gh api|actions/checkout|persist-credentials|GITHUB_TOKEN|token:' \
/tmp/github-release-job.yaml || trueRepository: photon-hq/buildspace
Length of output: 182
🏁 Script executed:
#!/bin/bash
set -euo pipefail
WF=".github/workflows/typescript-monorepo-release.yaml"
OUT="/tmp/github-release-job.yaml"
# Extract the github-release job block under jobs:
awk -v out="$OUT" '
BEGIN {injob=0}
/^jobs:/ {next}
# Start when we hit " github-release:"
/^ github-release:/ {
injob=1
}
# Stop when we hit the next top-level job key " <name>:"
injob && /^ [A-Za-z0-9_-]+:/ && $0 !~ /^ github-release:/ {
injob=0
}
injob {print}
' "$WF" > "$OUT"
echo "=== Extracted github-release job (first ~220 lines) ==="
nl -ba "$OUT" | sed -n '1,220p'
echo "=== Search for credential-requiring operations ==="
rg -n --hidden --no-ignore -S \
'git push|git tag|git commit|persist-credentials|gh release|gh api|actions/checkout|GITHUB_TOKEN|token[:=]' \
"$OUT" || trueRepository: photon-hq/buildspace
Length of output: 182
🏁 Script executed:
#!/bin/bash
set -euo pipefail
WF=".github/workflows/typescript-monorepo-release.yaml"
echo "=== Lines around github-release ==="
rg -n "github-release:" "$WF" || true
start=$(rg -n "github-release:" "$WF" | head -n1 | cut -d: -f1 || echo 140)
# print a window around it
lo=$((start-40))
hi=$((start+220))
sed -n "${lo},${hi}p" "$WF"
echo "=== Steps inside github-release that might need auth/push ==="
# Roughly limit to the github-release region by matching from github-release: down until next job key
awk '
/^ github-release:/ {injob=1}
injob {print}
injob && /^ [A-Za-z0-9_-]+:/ && $0 !~ /^ github-release:/ {exit}
' "$WF" | rg -n -S 'git push|git tag|git commit|persist-credentials|gh release|gh api|actions/checkout|GITHUB_TOKEN|token[:=]' || trueRepository: photon-hq/buildspace
Length of output: 4333
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the create-github-release block implementation in this repo
root=".github/blocks"
echo "=== Find create-github-release files ==="
fd -a -H "create-github-release" "$root" || true
fd -a -H "*create-github-release*" "$root" || true
# If there are multiple matches, list them
mapfile -t files < <(fd -a -H "create-github-release" "$root" -t f 2>/dev/null || true)
if [ "${`#files`[@]}" -eq 0 ]; then
# fallback: grab anything containing the name
mapfile -t files < <(fd -a -H "*create-github-release*" "$root" -t f 2>/dev/null || true)
fi
echo "=== Candidate files ==="
printf '%s\n' "${files[@]:0:30}"
# Search for any git push / checkout / credential persistence usage
for f in "${files[@]:0:30}"; do
echo "--- Searching: $f"
rg -n -S 'git push|git commit|git tag|persist-credentials|actions/checkout|gh api|gh release|GITHUB_TOKEN|token' "$f" || true
doneRepository: photon-hq/buildspace
Length of output: 541
🏁 Script executed:
#!/bin/bash
set -euo pipefail
DIR=".github/blocks/create-github-release"
echo "=== ls $DIR (depth-limited) ==="
find "$DIR" -maxdepth 3 -type f | head -n 200
echo "=== Search for credential-requiring git operations ==="
# Search in all files under the directory for git/credential/push usage
rg -n -S --hidden --no-ignore \
'git push|git commit|git tag\b|git fetch|git remote|persist-credentials|checkout@|GITHUB_TOKEN|actions/checkout|gh release|gh api' \
"$DIR" || true
echo "=== Search for token usage patterns ==="
rg -n -S --hidden --no-ignore \
'app-private-key|app-id|github-token|token[:=]|JWT|octokit|`@actions/github`|request\.headers|Authorization' \
"$DIR" || trueRepository: photon-hq/buildspace
Length of output: 941
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE=".github/blocks/create-github-release/action.yaml"
wc -l "$FILE"
sed -n '1,140p' "$FILE"Repository: photon-hq/buildspace
Length of output: 2682
Set persist-credentials: false on the github-release checkout.
The github-release job only enumerates existing tags (git tag -l) and creates the GitHub Release via API (softprops/action-gh-release using the app/GitHub token), so persisting checkout credentials is unnecessary and increases token exposure surface.
Suggested patch
- name: Checkout
uses: actions/checkout@v5
with:
# The release/<date>.N counter below counts existing tags; the default
# shallow checkout fetches no tags, so N was always 0 and a second
# same-day release reused (and overwrote) the first one's tag.
fetch-depth: 0
+ persist-credentials: false🧰 Tools
🪛 zizmor (1.25.2)
[warning] 152-158: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
🤖 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/typescript-monorepo-release.yaml around lines 154 - 158,
In the github-release job update the actions/checkout step (the one that
currently sets fetch-depth: 0) to add persist-credentials: false so credentials
are not retained for that checkout; locate the checkout step inside the
github-release job and add the persist-credentials: false key alongside
fetch-depth: 0 to limit token exposure (this affects the checkout action used
prior to git tag -l and softprops/action-gh-release).
Source: Linters/SAST tools
Four bugs in the release workflows, each verifiable by reading the code, each fixed with the smallest change that restores the documented intent. Per fix: what breaks, why, and why the fix can't change anything that works today.
1.
update-skills.yaml— releases with large diffs kill the job (E2BIG)gather-changesbuilt the release diff withDIFF=$(git diff ...)and then ranexport DIFFbefore invokingpython3. On Linux a single environment variable over ~128 KiB makesexecve()fail withE2BIG("Argument list too long"), so any release whose diff passed that limit failed the job — the 100k-char truncation lived inside the Python process that never got to start.81a89f4 fixed exactly this in
update-docs.yaml("write diff to file");update-skills.yamlis a copy of that code and kept the broken version. This ports the fix verbatim: write the diff to${RUNNER_TEMP}/changes.diff, pass the path through the environment, read and truncate in Python.Can't-regress argument: for diffs that worked before (under 128 KiB) the output is byte-identical apart from a trailing newline; diffs that crashed the job now succeed.
2.
go-build— thegoos: windowspath never workedThe build step runs
go build -o "<name>"and the package step runsmv "<name>.exe"whengoos=windows.go build -ouses the output name verbatim — it does not append.exe— so the filemvlooks for has never existed, and every windows invocation fails at the package step. The build step now appends the same.exesuffix the package step expects.Can't-regress argument: for darwin/linux,
SUFFIX=""and the command is character-for-character what it was. No current caller builds windows (go-service-releaseruns darwin/arm64 and linux/amd64), so nothing live changes at all; the windows path goes from provably broken to consistent with packaging.3.
typescript-monorepo-release.yaml— a second same-day release overwrites the firstThe
github-releasejob picks its tag by counting existingrelease/<date>.*tags and using the count as the.Nsuffix. Its checkout is the default shallow one, which fetches no tags, sogit tag -lalways returned nothing,Nwas always 0, and a second release on the same day reusedrelease/<date>.0.softprops/action-gh-releaseupdates an existing release when the tag exists, so run #2 silently replaced run #1's release notes.Fix:
fetch-depth: 0on that checkout (fetches all branches and tags).bump-versionsandnpm-publishin the same workflow already check out withfetch-depth: 0.Can't-regress argument: the only behavior change is the counter seeing real tags — the documented purpose of the suffix.
4.
update-docs.yaml+update-skills.yaml— one CODEOWNERS team entry disables all reviewer requestsThe reviewer extraction comment says "not @org/team", but
grep -oE '@[a-zA-Z0-9_-]+'stops at the slash and emits the bare@orgout of every team entry. GitHub's request-reviewers call is all-or-nothing: one invalid login rejects the whole request, and thegh pr editruns under|| true, so a single team entry in a caller repo's CODEOWNERS silently turned reviewer assignment off for every reviewer. The pattern now matches the full token and drops anything containing a slash — GitHub logins can't contain/, so every dropped entry was already guaranteed-invalid.The grep is also wrapped in
|| true: these steps run underpipefail, and a CODEOWNERS with no@entries at all madegrepexit 1 and fail the whole job.Can't-regress argument: every login the new filter emits, the old one emitted too; the removed entries are ones the API rejects categorically.
Test plan
yaml.safe_loadpasses on all 42 workflow/block files after the edits.bash -npasses on every editedrun:block; the embedded Python heredoc compiles.with:keys) passes.Risk / rollback
The four fixes are independent — each is its own commit touching disjoint hunks; revert any single commit to roll one back. No inputs, outputs, or interfaces change.
🤖 Generated with Claude Code
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Summary by CodeRabbit
.exefile extension