Skip to content

Bump OpenCode pin

Bump OpenCode pin #3

Workflow file for this run

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"