From c6f9f566b8362f2ca2ef0991f1ac1cb562b4354d Mon Sep 17 00:00:00 2001 From: svczero Date: Thu, 11 Jun 2026 12:39:07 -0700 Subject: [PATCH] fix(ci): install the Go toolchain go.mod requires; make BLOCKER opt-in The Semantic Analysis check failed on every PR for two reasons: 1. setup-go installed Go 1.24, but go.mod requires 1.26.3, and sfw runs the loader with GOTOOLCHAIN=local -- so `go list` refused: "go.mod requires go >= 1.26.3 (running go 1.24.13; GOTOOLCHAIN=local)". Use go-version-file: go.mod so CI installs exactly what go.mod requires. 2. BLOCKER mode (fail on ANY semantic change) was applied to every pull_request, so intentional feature changes failed with "Logic change detected in safe refactor!". BLOCKER is now opt-in via a 'semantic-safe' label; all other PRs run in 'check' mode (report the diff, don't fail). Also drop the dead vendoring + GOFLAGS=-mod=vendor (GetHardenedEnv strips GOFLAGS and forces -mod=readonly, so vendoring never took effect), and print sfw's actual error on failure instead of swallowing it. --- .github/workflows/semantic_analysis.yml | 27 +++++++++++++++---------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/.github/workflows/semantic_analysis.yml b/.github/workflows/semantic_analysis.yml index a502095..93fba42 100644 --- a/.github/workflows/semantic_analysis.yml +++ b/.github/workflows/semantic_analysis.yml @@ -24,7 +24,10 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.24' + # Match the version go.mod requires. sfw runs the loader with + # GOTOOLCHAIN=local, so a mismatched toolchain can't auto-upgrade and + # `go list` refuses with "go.mod requires go >= X (running go Y)". + go-version-file: 'go.mod' cache: true - name: Build SFW @@ -36,18 +39,19 @@ jobs: # Verify binary works ./bin/sfw --version || ./bin/sfw help || true - - name: Vendor Dependencies - # Vendor modules into workspace so they're available inside the sandbox. - # The sandbox only mounts the workspace, not GOMODCACHE (/home/runner/go/pkg/mod). - run: go mod vendor - - name: Determine Mode id: mode + env: + PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }} run: | - if [[ "${{ github.event_name }}" == "pull_request" ]]; then - echo "mode=BLOCKER" >> $GITHUB_OUTPUT + # BLOCKER fails the build on ANY semantic change, so it is opt-in: it + # applies only to PRs labeled 'semantic-safe' (claimed behavior-preserving + # refactors). Every other PR runs in 'check' mode, which reports the + # semantic diff without failing on intentional logic changes. + if [[ "${{ github.event_name }}" == "pull_request" ]] && [[ ",${PR_LABELS}," == *",semantic-safe,"* ]]; then + echo "mode=BLOCKER" >> "$GITHUB_OUTPUT" else - echo "mode=check" >> $GITHUB_OUTPUT + echo "mode=check" >> "$GITHUB_OUTPUT" fi - name: Prepare Analysis Environment @@ -104,8 +108,6 @@ jobs: MODE: ${{ steps.mode.outputs.mode }} WORKTREE_DIR: ${{ steps.prep.outputs.worktree_dir }} HAS_GO: ${{ steps.prep.outputs.has_go_files }} - # Use vendored dependencies - sandbox can't access GOMODCACHE - GOFLAGS: "-mod=vendor" GOPROXY: "off" run: | set -euo pipefail @@ -168,6 +170,8 @@ jobs: # Execute SFW diff (sfw handles its own sandboxing internally) if ! OUTPUT=$(./bin/sfw diff "$OLD_FILE" "$NEW_FILE" 2>&1); then echo "::error::sfw failed to process $NEW_FILE_REF" + # Surface the tool's actual error instead of swallowing it. + printf '%s\n' "$OUTPUT" | sed 's/^/ [sfw] /' ERROR_COUNT=$((ERROR_COUNT + 1)) continue fi @@ -175,6 +179,7 @@ jobs: # Validate JSON if ! echo "$OUTPUT" | jq -e . >/dev/null 2>&1; then echo "::error::Invalid JSON output for $NEW_FILE_REF" + printf '%s\n' "$OUTPUT" | sed 's/^/ [sfw] /' ERROR_COUNT=$((ERROR_COUNT + 1)) continue fi