From cce99f2bb56479602c99cd6806e3e0274b8231ca Mon Sep 17 00:00:00 2001 From: Claude Code Date: Thu, 18 Jun 2026 15:51:14 +0000 Subject: [PATCH 1/2] Automate Go version bumps via a go.mod-only checker --- .github/workflows/check-go-version.yml | 99 ++++++++++++++++++++++++++ CLAUDE.md | 16 ++++- 2 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/check-go-version.yml diff --git a/.github/workflows/check-go-version.yml b/.github/workflows/check-go-version.yml new file mode 100644 index 0000000..e25ee5b --- /dev/null +++ b/.github/workflows/check-go-version.yml @@ -0,0 +1,99 @@ +name: Check Go Version + +on: + schedule: + # Run every Monday at 9 AM UTC + - cron: '0 9 * * 1' + workflow_dispatch: # Allow manual triggering + +permissions: + contents: write + pull-requests: write + +jobs: + check-version: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Get current Go version from go.mod + id: current + run: | + # go.mod is the canonical and only source of truth for the repository's + # Go version. Workflows derive their version from it via + # `go-version-file: go.mod`, so there is nothing else to update. + CURRENT_VERSION=$(grep -E '^go [0-9]' go.mod | awk '{print $2}') + echo "Current Go version in go.mod: $CURRENT_VERSION" + echo "version=$CURRENT_VERSION" >> "$GITHUB_OUTPUT" + + - name: Get latest stable Go version + id: latest + run: | + # Fetch the latest stable Go version from the official releases page. + LATEST_VERSION=$(curl -s https://go.dev/VERSION?m=text | head -n 1 | sed 's/go//') + echo "Latest stable Go version: $LATEST_VERSION" + echo "version=$LATEST_VERSION" >> "$GITHUB_OUTPUT" + + - name: Open a pull request if outdated + env: + GH_TOKEN: ${{ github.token }} + CURRENT_VERSION: ${{ steps.current.outputs.version }} + LATEST_VERSION: ${{ steps.latest.outputs.version }} + run: | + echo "Current: $CURRENT_VERSION" + echo "Latest: $LATEST_VERSION" + + if [ "$CURRENT_VERSION" = "$LATEST_VERSION" ]; then + echo "Go version is up to date!" + exit 0 + fi + + echo "Go version is outdated." + BRANCH="automated/update-go-version-$LATEST_VERSION" + + # Skip if a PR for this exact bump is already open. + EXISTING_PR=$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number') + if [ -n "$EXISTING_PR" ]; then + echo "PR #$EXISTING_PR already open for branch $BRANCH, skipping." + exit 0 + fi + + # go.mod is the only place a Go version lives. Update the `go` directive + # (and the `toolchain` line, if present); workflows need no changes + # because they read the version from go.mod. + sed -i -E "s/^go [0-9]+(\.[0-9]+)*$/go $LATEST_VERSION/" go.mod + sed -i -E "s/^toolchain go[0-9]+(\.[0-9]+)*$/toolchain go$LATEST_VERSION/" go.mod + + git config user.name "Claude Code" + git config user.email "herve.quiroz+claude@gmail.com" + git checkout -b "$BRANCH" + git add go.mod + git commit -m "Update Go version from $CURRENT_VERSION to $LATEST_VERSION" + # This branch is exclusively bot-owned and deterministically named per + # version, so a plain force push is safe and avoids lease failures on a + # fresh runner that has no remote-tracking ref for it. + git push --force origin "$BRANCH" + + gh pr create \ + --head "$BRANCH" \ + --base main \ + --title "Update Go version from $CURRENT_VERSION to $LATEST_VERSION" \ + --label "claude" \ + --body "$(cat <.go` (for example `ecs_world.go`, `ecs_store.go`), with matching tests `ecs__test.go`. The package doc lives in `ecs/ecs_doc.go`. * Keep files small and single-responsibility; split by responsibility, not by technical layer. -### Go version and dependencies +### Go Version Consistency + +`go.mod` is the single source of truth for this repository's Go version — it is the only place a Go version is written, and the workflows already derive their version from it via `go-version-file: go.mod`: + +```yaml +- uses: actions/setup-go@v5 + with: + go-version-file: go.mod + check-latest: true +``` + +Never hardcode a `go-version:` literal in a workflow. The scheduled `check-go-version.yml` workflow compares `go.mod`'s declared version against the latest stable Go release and opens a PR bumping the single `go` directive when it falls behind. Because nothing else carries the version, a bump is a one-line change. + +### Dependencies -* Go 1.26 (see `go.mod`). The code relies on range-over-func and the `iter` package. * No third-party dependencies, and that is a deliberate property of a foundational library. A new dependency is a significant decision; if one is ever genuinely needed, call it out prominently in the PR description (name, version, purpose, justification, and whether it is test-only). ### Verification before pushing From a300c743b29a14c37dd7db403a329e4ee8d8be0f Mon Sep 17 00:00:00 2001 From: Claude Code Date: Thu, 18 Jun 2026 17:03:47 +0000 Subject: [PATCH 2/2] Open an issue instead of creating the bump PR programmatically The scheduled Go version checker now opens a claude-labelled issue requesting the bump rather than building the branch, commit, and PR itself. The autonomous Claude agent produces the actual PR, which is less brittle than scripting git/gh in the workflow. Drops the contents/pull-requests write permissions in favour of issues: write, and updates the CLAUDE.md invariant to match. --- .github/workflows/check-go-version.yml | 56 +++++++++----------------- CLAUDE.md | 2 +- 2 files changed, 19 insertions(+), 39 deletions(-) diff --git a/.github/workflows/check-go-version.yml b/.github/workflows/check-go-version.yml index e25ee5b..f609b23 100644 --- a/.github/workflows/check-go-version.yml +++ b/.github/workflows/check-go-version.yml @@ -7,8 +7,7 @@ on: workflow_dispatch: # Allow manual triggering permissions: - contents: write - pull-requests: write + issues: write jobs: check-version: @@ -34,7 +33,7 @@ jobs: echo "Latest stable Go version: $LATEST_VERSION" echo "version=$LATEST_VERSION" >> "$GITHUB_OUTPUT" - - name: Open a pull request if outdated + - name: Open an issue if outdated env: GH_TOKEN: ${{ github.token }} CURRENT_VERSION: ${{ steps.current.outputs.version }} @@ -49,47 +48,28 @@ jobs: fi echo "Go version is outdated." - BRANCH="automated/update-go-version-$LATEST_VERSION" - - # Skip if a PR for this exact bump is already open. - EXISTING_PR=$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number') - if [ -n "$EXISTING_PR" ]; then - echo "PR #$EXISTING_PR already open for branch $BRANCH, skipping." + TITLE="Update Go version from $CURRENT_VERSION to $LATEST_VERSION" + + # Skip if an open issue for this exact bump already exists. Opening an + # issue (rather than building the PR here) keeps this workflow simple and + # lets the autonomous Claude agent produce the bump PR. + EXISTING_ISSUE=$(gh issue list --search "$TITLE in:title" --state open --label claude --json number,title --jq "map(select(.title == \"$TITLE\")) | .[0].number // empty") + if [ -n "$EXISTING_ISSUE" ]; then + echo "Issue #$EXISTING_ISSUE already open for this bump, skipping." exit 0 fi - # go.mod is the only place a Go version lives. Update the `go` directive - # (and the `toolchain` line, if present); workflows need no changes - # because they read the version from go.mod. - sed -i -E "s/^go [0-9]+(\.[0-9]+)*$/go $LATEST_VERSION/" go.mod - sed -i -E "s/^toolchain go[0-9]+(\.[0-9]+)*$/toolchain go$LATEST_VERSION/" go.mod - - git config user.name "Claude Code" - git config user.email "herve.quiroz+claude@gmail.com" - git checkout -b "$BRANCH" - git add go.mod - git commit -m "Update Go version from $CURRENT_VERSION to $LATEST_VERSION" - # This branch is exclusively bot-owned and deterministically named per - # version, so a plain force push is safe and avoids lease failures on a - # fresh runner that has no remote-tracking ref for it. - git push --force origin "$BRANCH" - - gh pr create \ - --head "$BRANCH" \ - --base main \ - --title "Update Go version from $CURRENT_VERSION to $LATEST_VERSION" \ + gh issue create \ + --title "$TITLE" \ --label "claude" \ --body "$(cat <