Skip to content

chore: pin the Go toolchain via .go-version and GOTOOLCHAIN - #21

Merged
kwrkb merged 2 commits into
masterfrom
chore/pin-go-toolchain
Sep 12, 2026
Merged

kwrkb merged 2 commits into
masterfrom
chore/pin-go-toolchain

Conversation

@kwrkb

@kwrkb kwrkb commented Sep 11, 2026

Copy link
Copy Markdown
Owner

なぜ

stale 検出ゲートは「再ビルド → git diff --exit-code」なので bit-identical が必要で、そのために Go のパッチ版一致が要る(LESSONS #26 の実測)。ところがその要件はコメントに書いてあるだけで、何も強制していなかった。

  • go.mod の go 1.26.5 は下限であってダウングレードは強制できない
  • GOTOOLCHAIN の既定は auto = ローカルが 1.26.5 以上なら黙ってそちらが使われる

結果、ローカル go1.27.1 で ./build.sh を回すと 18 バイナリが差分として出る。しかもそれは正常な再ビルド出力と見分けがつかない。PR #20 で Codex が P1「バイナリが stale」を指摘した原因もこれ。

何をしたか

  • ルートに .go-version(1.26.5)を追加。パッチ版の定義箇所をここ1つに集約。
  • build.sh が export GOTOOLCHAIN=go$(cat .go-version) で強制。go version を echo する行より前に置いたので、表示される版がそのまま「固定が効いた証拠」になる。未取得なら Go が自動ダウンロードするため開発者の事前準備は不要。
  • build.ps1 も同様。ただし $env: は呼び出し元セッションを汚染するため(LESSONS #43)、既存の退避/finally 復元ブロックに GOTOOLCHAIN を追加。go version 表示より先に設定するので、表示と実ビルドの版がずれない。
  • CI は go-version-file: .go-version(7ジョブ)に変更し、重複していた GO_VERSION env を削除。paths トリガにも .go-version を追加(ゲートが自身の入力を守る。LESSONS #27)。
  • 版番号を直書きしていたドキュメント(CLAUDE.md / AGENTS.md / README 2件)を .go-version への参照に置換。

検証

  • ./build.sh を環境変数なしで実行し [go1.26.5] と表示されること、リポジトリ外の CWD から実行しても動くこと(cd を先頭へ移動した回帰)を確認。
  • binary-neutral: 全ビルド後もコミット済み 21 バイナリに差分ゼロ。同時に、master 上の全バイナリが最新(stale でない)ことも確認できた。
  • sh -n build.sh / pwsh パーサでの構文チェック済み。

補足

バージョンを 1.26.5 のまま据え置いています。現在 1.26.5 には到達可能な stdlib 脆弱性が3件(GO-2026-6218 / 6090 / 5972、いずれも 1.26.6 で修正済み、最新パッチは 1.26.8)あり、CI はこれを ::warning:: で握り潰しています。版の引き上げは全21バイナリの再ビルドを伴い PR #20 と競合するため、#20 のマージ後に別 PR で行います。

🤖 Generated with Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0ffa770de5

ℹ️ 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".

Comment thread build.sh Outdated
# go.mod の `go` ディレクティブは下限でしかないため、ローカルに新しい Go があると
# 黙ってそちらが使われ、正常な再ビルド出力と見分けのつかない差分が出る(stale ゲートが落ちる)。
# 指定版が無ければ Go が自動ダウンロードするので、開発者側の事前準備は不要。
GOTOOLCHAIN="go$(cat .go-version)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Strip CR from the shell toolchain version

On Windows Git Bash with core.autocrlf=true, .go-version is checked out with CRLF because the repository has no .gitattributes rule for it. Command substitution removes the trailing LF but retains CR, so this exports GOTOOLCHAIN=go1.26.5\r; Go then exits with invalid GOTOOLCHAIN before any build. Since build.sh explicitly supports Git Bash, strip the CR/whitespace while reading the version or force .go-version to LF.

AGENTS.md reference: AGENTS.md:L82-L82

Useful? React with 👍 / 👎.

@kwrkb

kwrkb commented Sep 12, 2026

Copy link
Copy Markdown
Owner Author

P2「Strip CR from the shell toolchain version」に対応しました(087a0d9)。指摘は正しく、ローカルで再現できました。

$ printf '1.26.5\r\n' > .go-version
$ GOTOOLCHAIN="go$(cat .go-version)" go version
go: invalid GOTOOLCHAIN "go1.26.5\r"

2層で防いでいます。

  1. .gitattributes を新規追加(リポジトリに存在しなかったのも指摘どおり)。.go-version と *.sh / *.ps1 / 拡張子なしの OS 分岐 dispatcher 7本を text eol=lf に固定。あわせてコミット済みネイティブバイナリを binary として明示しました(*-linux-amd64 / *-darwin-arm64 / *.exe)。bit-identical ゲートが比較するバイトを改行変換に触らせないためで、これまで無保護でした。
  2. build.sh の読み取り側で空白類を除去(tr -d "[:space:]" < .go-version)。.gitattributes は新規 checkout にしか効かないため、既に CRLF でクローンしてある環境を救う必要があります。

build.ps1 は (Get-Content -Raw).Trim() で読んでいるため CR も除去され、対処不要でした。

検証: .go-version を CRLF で書いた状態と LF の状態の両方で ./build.sh settings-advisor が [go1.26.5] を表示することを確認。.gitattributes 追加による再正規化差分がゼロ(バイナリ21本に変化なし)であることも確認済みです。CI 全7ジョブ green。

kwrkb and others added 2 commits September 12, 2026 11:58
The deterministic rebuild gate requires a bit-identical build, which
requires the exact Go patch version. That requirement was only stated in
comments, and nothing enforced it: go.mod's `go 1.26.5` is a lower bound
and GOTOOLCHAIN defaults to `auto`, so a newer local toolchain is used
silently. Running ./build.sh with go1.27.1 produced 18 modified binaries
that are indistinguishable from a legitimate rebuild.

- Add .go-version as the single definition of the pinned patch version.
- build.sh exports GOTOOLCHAIN from it, before the line that echoes
  `go version`, so the printed version is proof the pin took effect.
  Go downloads the toolchain on demand, so no developer setup is needed.
- build.ps1 does the same, saving and restoring $env:GOTOOLCHAIN in the
  existing finally block so it does not leak into the caller's session.
- CI reads the same file via setup-go's go-version-file, replacing the
  duplicated GO_VERSION env, and .go-version joins the paths triggers so
  the gate covers its own inputs.
- Point the docs at .go-version instead of repeating the number.

Binary-neutral: a full ./build.sh leaves all 21 committed binaries
unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
build.sh supports Git Bash and WSL, but the repo had no .gitattributes,
so with core.autocrlf=true `.go-version` is checked out as CRLF. Command
substitution strips the LF and keeps the CR, so build.sh exported
`GOTOOLCHAIN=go1.26.5\r` and Go exited with
`invalid GOTOOLCHAIN "go1.26.5\r"` before building anything. Reproduced
locally.

Defend at both layers:

- Add .gitattributes pinning .go-version and the shell scripts (build.sh,
  build.ps1, and the extensionless OS-dispatchers) to LF, and marking the
  committed native binaries as binary so nothing ever rewrites the bytes
  the bit-identical gate compares.
- build.sh strips whitespace when reading the file, which also rescues
  clones that already have CRLF on disk.

build.ps1 was already safe: it reads the file with .Trim().

Verified: ./build.sh still reports [go1.26.5] with the file written as
CRLF and as LF, and adding .gitattributes renormalizes nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kwrkb
kwrkb force-pushed the chore/pin-go-toolchain branch from 087a0d9 to eac1b18 Compare September 12, 2026 02:58
@kwrkb
kwrkb merged commit 9046001 into master Sep 12, 2026
7 checks passed
@kwrkb
kwrkb deleted the chore/pin-go-toolchain branch September 12, 2026 03:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant