Bump OpenCode pin #3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Bump OpenCode pin | |
| # Weekly cron + manual dispatch. Compares the version in | |
| # .github/opencode-version.txt against the latest published OpenCode release. | |
| # If they drift, opens a PR bumping the pin. The PR triggers the full Tests | |
| # workflow (Linux/macOS/Windows units + Linux/macOS/Windows E2E), so a bad | |
| # OpenCode release fails on the PR before merge instead of breaking main. | |
| # | |
| # Why this exists: all three E2E harnesses (Linux Docker, macOS native, | |
| # Windows native) read the pin from .github/opencode-version.txt to avoid | |
| # the GitHub API anonymous rate-limit on macOS runners (60 req/hr per | |
| # egress IP — repeatedly hit on shared macOS infra). Pinning is the right | |
| # call for stability, but pinning forever means we silently drift behind | |
| # upstream and miss real OpenCode regressions. This workflow is the | |
| # auto-refresh that closes the loop. | |
| # | |
| # This workflow uses `gh` with the default GITHUB_TOKEN which gets the | |
| # 1000 req/hr authenticated GitHub API budget, so it never hits the | |
| # anonymous rate limit the OpenCode installer does. | |
| # | |
| # IMPORTANT — `GITHUB_TOKEN` and downstream workflow triggers: | |
| # Per https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow, | |
| # events created by `GITHUB_TOKEN` (push, pull_request, etc.) do NOT | |
| # trigger new workflow runs. So opening this PR with `gh pr create` and | |
| # pushing the branch with the default token will NOT auto-run Tests. | |
| # `workflow_dispatch` and `repository_dispatch` are exceptions, so we | |
| # dispatch Tests explicitly after opening the PR. Tests' `workflow_dispatch` | |
| # input takes the branch ref so the run is gated on the bumped pin. | |
| # | |
| # If you want PR-driven runs (so Tests appears as a check on the PR | |
| # itself, not as a separate workflow_dispatch run), set the | |
| # `BUMP_OPENCODE_PAT` repo secret to a PAT with `repo` + `workflow` scope. | |
| # When present we use it for `git push` and `gh pr create`; PR events | |
| # from a PAT DO trigger downstream workflows. | |
| on: | |
| schedule: | |
| # Every Monday 06:00 UTC. Far enough from typical release Friday | |
| # evenings that any urgent post-release fixes are usually in. | |
| - cron: "0 6 * * 1" | |
| workflow_dispatch: {} | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| jobs: | |
| bump: | |
| name: Check for OpenCode upstream drift | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Read current pin | |
| id: current | |
| run: | | |
| version=$(tr -d '[:space:]' < .github/opencode-version.txt) | |
| if [ -z "$version" ]; then | |
| echo "::error::.github/opencode-version.txt is empty" | |
| exit 1 | |
| fi | |
| echo "version=$version" >> "$GITHUB_OUTPUT" | |
| echo "Current pin: $version" | |
| - name: Resolve latest OpenCode release | |
| id: latest | |
| env: | |
| # gh uses GITHUB_TOKEN automatically; explicit env makes the auth | |
| # boundary visible in logs and lets `gh` skip its login probe. | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # The OpenCode installer hits this same endpoint anonymously | |
| # (see https://opencode.ai/install). With auth we get 1000 req/hr, | |
| # so this never hits the rate limit that motivates the pin. | |
| tag=$(gh api repos/anomalyco/opencode/releases/latest --jq '.tag_name') | |
| if [ -z "$tag" ] || [ "$tag" = "null" ]; then | |
| echo "::error::Failed to resolve latest OpenCode release tag" | |
| exit 1 | |
| fi | |
| # Strip leading 'v' if present (1.14.39 not v1.14.39). | |
| version="${tag#v}" | |
| echo "version=$version" >> "$GITHUB_OUTPUT" | |
| echo "Latest release: $version (tag: $tag)" | |
| - name: Compare versions | |
| id: compare | |
| run: | | |
| if [ "${{ steps.current.outputs.version }}" = "${{ steps.latest.outputs.version }}" ]; then | |
| echo "Pin is already at latest. Nothing to do." | |
| echo "drift=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "Drift: pin=${{ steps.current.outputs.version }} latest=${{ steps.latest.outputs.version }}" | |
| echo "drift=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Update pin file | |
| if: steps.compare.outputs.drift == 'true' | |
| run: | | |
| echo "${{ steps.latest.outputs.version }}" > .github/opencode-version.txt | |
| - name: Open PR | |
| if: steps.compare.outputs.drift == 'true' | |
| id: pr | |
| env: | |
| # Prefer a PAT if the repo owner has set one — PR events from a PAT | |
| # actually trigger downstream workflows (Tests). Fall back to the | |
| # default GITHUB_TOKEN, which works for opening the PR but won't | |
| # auto-trigger Tests; the next step dispatches Tests explicitly. | |
| GH_TOKEN: ${{ secrets.BUMP_OPENCODE_PAT || secrets.GITHUB_TOKEN }} | |
| PAT_PRESENT: ${{ secrets.BUMP_OPENCODE_PAT != '' && 'yes' || '' }} | |
| run: | | |
| set -euo pipefail | |
| old="${{ steps.current.outputs.version }}" | |
| new="${{ steps.latest.outputs.version }}" | |
| branch="ci/bump-opencode-${new}" | |
| # If the bump branch already exists (we opened the same PR previously | |
| # and it's still open / unmerged), just refresh it. Otherwise create | |
| # a new branch. | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| # Push using the same token we'll use for `gh pr create`. When PAT is | |
| # set, this writes the branch as the PAT user and triggers PR events | |
| # normally. With the default GITHUB_TOKEN, the push lands but won't | |
| # trigger Tests via the `pull_request` event — see the dispatch step | |
| # below. | |
| remote_url="https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" | |
| git remote set-url origin "$remote_url" | |
| git checkout -b "$branch" | |
| git add .github/opencode-version.txt | |
| git commit -m "ci(opencode): bump pinned OpenCode to v${new} | |
| Auto-generated by .github/workflows/bump-opencode.yml. | |
| Bumps .github/opencode-version.txt from ${old} -> ${new}. | |
| All three E2E harnesses (Linux Docker, macOS native, Windows native) | |
| read this file, so PR-time CI exercises the new OpenCode version on | |
| every supported platform before merge. If any platform regresses, | |
| the PR will fail and you'll know immediately." | |
| git push -f origin "$branch" | |
| echo "branch=$branch" >> "$GITHUB_OUTPUT" | |
| # Surface PAT presence to downstream steps via this step's outputs | |
| # so we don't have to re-evaluate the secret in another step (where | |
| # secret-conditional logic is awkward). | |
| if [ -n "${PAT_PRESENT:-}" ]; then | |
| echo "trigger_note=_Tests is triggered automatically by the \`pull_request\` event (PAT in use)._" >> "$GITHUB_OUTPUT" | |
| echo "needs_dispatch=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "trigger_note=_PR opened with \`GITHUB_TOKEN\`, which does NOT trigger downstream workflows. The bump job dispatches Tests separately via \`workflow_dispatch\` against this branch — look for the matching \"Tests\" run in the Actions tab. For PR-driven runs that appear as a check on this PR itself, set the \`BUMP_OPENCODE_PAT\` repo secret to a PAT with \`repo\` + \`workflow\` scope._" >> "$GITHUB_OUTPUT" | |
| echo "needs_dispatch=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| # Open the PR (or surface the existing one if open). Body lists the | |
| # release notes link so reviewer can scan upstream changes quickly. | |
| existing=$(gh pr list --head "$branch" --state open --json number --jq '.[0].number' || echo "") | |
| if [ -n "$existing" ]; then | |
| echo "PR already open: #$existing — branch was force-pushed." | |
| exit 0 | |
| fi | |
| gh pr create \ | |
| --head "$branch" \ | |
| --base main \ | |
| --title "ci(opencode): bump pinned OpenCode to v${new}" \ | |
| --body "$(cat <<EOF | |
| Automated bump of \`.github/opencode-version.txt\` from \`${old}\` to \`${new}\`. | |
| Upstream release notes: https://github.com/anomalyco/opencode/releases/tag/v${new} | |
| ### What this changes | |
| All three E2E harnesses now install OpenCode v${new}: | |
| - \`tests/docker/Dockerfile.linux-x64\` (Linux Docker E2E) | |
| - \`tests/macos-e2e/run.sh\` (macOS native E2E) | |
| - \`tests/windows-e2e/run.ps1\` (Windows native E2E) | |
| ### Merge gate | |
| The Tests workflow on this PR exercises the new version against the | |
| full E2E matrix. If any platform regresses, **do not merge** — file | |
| an upstream issue or hold the bump until upstream fixes the | |
| regression. Pin will stay at \`${old}\` until this PR is merged. | |
| ${PAT_PRESENT:+_Tests runs automatically because this PR was opened with a PAT._} | |
| ${PAT_PRESENT:-_PR opened with default \`GITHUB_TOKEN\`. Tests does not auto-run on \`pull_request\` here; the bump workflow dispatched Tests via \`workflow_dispatch\` against this branch instead — see the Actions tab. Set \`BUMP_OPENCODE_PAT\` secret for PR-driven Tests checks._} | |
| EOF | |
| )" | |
| # When we don't have a PAT, the `pull_request` event from the bump PR | |
| # won't trigger Tests. Dispatch it explicitly so the bumped pin still | |
| # gets exercised end-to-end. Skipped when a PAT is configured because | |
| # the PR-event trigger covers it. | |
| - name: Dispatch Tests on bump branch (GITHUB_TOKEN fallback) | |
| if: steps.compare.outputs.drift == 'true' && steps.pr.outputs.needs_dispatch == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| branch="${{ steps.pr.outputs.branch }}" | |
| if [ -z "$branch" ]; then | |
| echo "::error::No bump branch recorded; cannot dispatch Tests." | |
| exit 1 | |
| fi | |
| # `workflow_dispatch` is the documented exception that lets | |
| # GITHUB_TOKEN trigger another workflow run. The dispatched run | |
| # appears in the Actions tab tagged to this branch but is NOT | |
| # attached as a PR check. That's fine — the cron+human review | |
| # is the merge gate, not branch protection. | |
| dispatch_started=$(date -u +"%Y-%m-%dT%H:%M:%SZ") | |
| gh workflow run tests.yml --ref "$branch" \ | |
| -f ref="$branch" | |
| echo "Waiting for Tests workflow dispatch to register..." | |
| run_id="" | |
| for attempt in 1 2 3 4 5 6; do | |
| run_id=$(gh run list \ | |
| --workflow tests.yml \ | |
| --branch "$branch" \ | |
| --event workflow_dispatch \ | |
| --limit 20 \ | |
| --json databaseId,createdAt \ | |
| --jq "map(select(.createdAt >= \"${dispatch_started}\")) | .[0].databaseId // \"\"") | |
| if [ -n "$run_id" ]; then | |
| break | |
| fi | |
| echo "Tests run not visible yet (attempt $attempt/6); retrying..." | |
| sleep 5 | |
| done | |
| if [ -z "$run_id" ]; then | |
| echo "::error::Tests dispatch did not create a workflow_dispatch run for $branch." | |
| exit 1 | |
| fi | |
| echo "Dispatched Tests run: $run_id" |