Skip to content

Update Opencode Commit #125

Update Opencode Commit

Update Opencode Commit #125

name: Update Opencode Commit
on:
workflow_dispatch:
schedule:
- cron: "0 1-23/2 * * *"
permissions:
contents: write
issues: write # Required for creating issues on CI failure
jobs:
update-opencode-commit:
name: Update Opencode Commit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Check for existing automated submodule failure issue
id: failure_issue_gate
uses: actions/github-script@v7
with:
script: |
const failureMarker = 'Automated submodule update failed';
const openAutomatedIssues = await github.paginate(
github.rest.issues.listForRepo,
{
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: 'automated',
per_page: 100,
},
);
const matchingIssues = openAutomatedIssues.filter((issue) =>
(issue.body || '').includes(failureMarker),
);
matchingIssues.sort((a, b) => b.number - a.number);
const existingIssue = matchingIssues[0] || null;
const hasOpenFailureIssue = Boolean(existingIssue);
const shouldBlock =
context.eventName === 'schedule' && hasOpenFailureIssue;
core.info(
`failure_issue_gate: event=${context.eventName}, has_open_failure_issue=${hasOpenFailureIssue}, should_block=${shouldBlock}`,
);
if (existingIssue) {
core.info(
`failure_issue_gate: using #${existingIssue.number} ${existingIssue.html_url}`,
);
}
core.setOutput('has_open_failure_issue', String(hasOpenFailureIssue));
core.setOutput(
'existing_failure_issue_number',
existingIssue ? String(existingIssue.number) : '',
);
core.setOutput(
'existing_failure_issue_url',
existingIssue ? existingIssue.html_url : '',
);
core.setOutput(
'existing_failure_issue_title',
existingIssue ? existingIssue.title : '',
);
core.setOutput('should_block', String(shouldBlock));
- name: Initialize opencode submodule
if: steps.failure_issue_gate.outputs.should_block != 'true'
run: |
# .gitmodules tracks opencode with an SSH URL. Actions runners
# typically lack SSH deploy keys, so rewrite to HTTPS inline.
git -c url."https://github.com/".insteadOf=git@github.com: \
-c url."https://github.com/".insteadOf=ssh://git@github.com/ \
submodule update --init --recursive packages/opencode
git submodule status --recursive
- name: Configure Git
if: steps.failure_issue_gate.outputs.should_block != 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Toolchain setup for running CI checks on the updated submodule.
# Steps match ci-pre-publish.yml to ensure parity with normal CI.
- name: Install Linux system dependencies
if: steps.failure_issue_gate.outputs.should_block != 'true'
run: |
# Required by opencode-broker (libpam-sys links against -lpam).
sudo apt-get update
sudo apt-get install -y libpam0g-dev
- name: Install Rust
if: steps.failure_issue_gate.outputs.should_block != 'true'
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Install just
if: steps.failure_issue_gate.outputs.should_block != 'true'
uses: extractions/setup-just@v3
- name: Setup Node.js
if: steps.failure_issue_gate.outputs.should_block != 'true'
uses: actions/setup-node@v5
with:
node-version: '20'
- name: Setup Bun
if: steps.failure_issue_gate.outputs.should_block != 'true'
uses: ./.github/actions/setup-bun
- name: Rust cache
if: steps.failure_issue_gate.outputs.should_block != 'true'
uses: Swatinem/rust-cache@v2
- name: Update opencode commit
id: update
if: steps.failure_issue_gate.outputs.should_block != 'true'
run: |
./scripts/update-opencode-commit.sh
# Validate the updated submodule before committing.
# Auto-fix formatting first, then run the full CI check suite.
# If checks fail, the workflow fails and the broken commit is never pushed.
- name: Auto-format and run CI checks
id: ci_checks
if: steps.failure_issue_gate.outputs.should_block != 'true' && steps.update.outputs.needs_update == 'true'
run: |
set -euo pipefail
# Auto-fix formatting issues introduced by the submodule update
just fmt || true
# If formatting produced changes, stage and commit them
if ! git diff --quiet; then
git add -A
git commit -m "style: auto-format after opencode submodule update" || true
fi
# Run the full CI check suite
just ci-checks
- name: Run e2e tests
id: e2e_checks
if: steps.failure_issue_gate.outputs.should_block != 'true' && steps.update.outputs.needs_update == 'true'
run: just ci-e2e
timeout-minutes: 30
- name: Verify OCI description label
id: verify_oci
if: steps.failure_issue_gate.outputs.should_block != 'true'
run: python3 scripts/extract-oci-description.py packages/core/src/docker/Dockerfile
- name: Commit and push if changed
id: commit_push
if: steps.failure_issue_gate.outputs.should_block != 'true'
run: |
if git diff --quiet; then
echo "No changes to commit."
{
echo "changed=false"
echo "pushed=false"
echo "commit_sha="
} >> "$GITHUB_OUTPUT"
exit 0
fi
# Keep superproject updates atomic: commit the submodule pointer and
# any root bun.lock refresh together when they change in the same run.
git add bun.lock packages/core/src/docker/Dockerfile packages/opencode
git commit -m "chore: update opencode commit"
# Fail fast before push if the staged submodule gitlink points to a
# commit that is not yet fetchable from the submodule remote.
./scripts/check-opencode-submodule-published.sh --from-index
commit_sha="$(git rev-parse HEAD)"
{
echo "changed=true"
echo "pushed=false"
echo "commit_sha=${commit_sha}"
} >> "$GITHUB_OUTPUT"
git push
echo "pushed=true" >> "$GITHUB_OUTPUT"
- name: Notice skipped update due to existing open failure issue
if: steps.failure_issue_gate.outputs.should_block == 'true'
env:
EXISTING_FAILURE_ISSUE_NUMBER: ${{ steps.failure_issue_gate.outputs.existing_failure_issue_number }}
EXISTING_FAILURE_ISSUE_URL: ${{ steps.failure_issue_gate.outputs.existing_failure_issue_url }}
EXISTING_FAILURE_ISSUE_TITLE: ${{ steps.failure_issue_gate.outputs.existing_failure_issue_title }}
run: |
set -euo pipefail
echo "::notice title=Update skipped::Scheduled run skipped because an open failure issue already exists (#${EXISTING_FAILURE_ISSUE_NUMBER}): ${EXISTING_FAILURE_ISSUE_TITLE} (${EXISTING_FAILURE_ISSUE_URL})"
- name: Write summary
if: always()
env:
FAILURE_ISSUE_GATE_OUTCOME: ${{ steps.failure_issue_gate.outcome }}
HAS_OPEN_FAILURE_ISSUE: ${{ steps.failure_issue_gate.outputs.has_open_failure_issue }}
EXISTING_FAILURE_ISSUE_NUMBER: ${{ steps.failure_issue_gate.outputs.existing_failure_issue_number }}
EXISTING_FAILURE_ISSUE_URL: ${{ steps.failure_issue_gate.outputs.existing_failure_issue_url }}
EXISTING_FAILURE_ISSUE_TITLE: ${{ steps.failure_issue_gate.outputs.existing_failure_issue_title }}
SHOULD_BLOCK: ${{ steps.failure_issue_gate.outputs.should_block }}
UPDATE_OUTCOME: ${{ steps.update.outcome }}
CI_CHECKS_OUTCOME: ${{ steps.ci_checks.outcome }}
E2E_CHECKS_OUTCOME: ${{ steps.e2e_checks.outcome }}
VERIFY_OCI_OUTCOME: ${{ steps.verify_oci.outcome }}
COMMIT_PUSH_OUTCOME: ${{ steps.commit_push.outcome }}
SUBMODULE_BRANCH: ${{ steps.update.outputs.submodule_branch }}
SUBMODULE_REPO_URL: ${{ steps.update.outputs.submodule_repo_url }}
CURRENT_SUBMODULE_COMMIT: ${{ steps.update.outputs.current_submodule_commit }}
LATEST_COMMIT: ${{ steps.update.outputs.latest_commit }}
CURRENT_PIN_VALUE: ${{ steps.update.outputs.current_pin_value }}
UPDATED_PIN_VALUE: ${{ steps.update.outputs.updated_pin_value }}
NEEDS_UPDATE: ${{ steps.update.outputs.needs_update }}
UPDATED: ${{ steps.update.outputs.updated }}
CHANGED: ${{ steps.commit_push.outputs.changed }}
PUSHED: ${{ steps.commit_push.outputs.pushed }}
COMMIT_SHA: ${{ steps.commit_push.outputs.commit_sha }}
run: |
set -euo pipefail
fallback() {
local value="${1:-}"
if [[ -z "${value}" ]]; then
echo "n/a"
else
echo "${value}"
fi
}
branch="$(fallback "${SUBMODULE_BRANCH:-}")"
repo_url="$(fallback "${SUBMODULE_REPO_URL:-}")"
current_submodule="$(fallback "${CURRENT_SUBMODULE_COMMIT:-}")"
latest_submodule="$(fallback "${LATEST_COMMIT:-}")"
current_pin="$(fallback "${CURRENT_PIN_VALUE:-}")"
updated_pin="$(fallback "${UPDATED_PIN_VALUE:-}")"
needs_update="$(fallback "${NEEDS_UPDATE:-}")"
updated="$(fallback "${UPDATED:-}")"
changed="$(fallback "${CHANGED:-}")"
pushed="$(fallback "${PUSHED:-}")"
has_open_failure_issue="$(fallback "${HAS_OPEN_FAILURE_ISSUE:-}")"
should_block="$(fallback "${SHOULD_BLOCK:-}")"
existing_failure_issue_number="$(fallback "${EXISTING_FAILURE_ISSUE_NUMBER:-}")"
existing_failure_issue_url="$(fallback "${EXISTING_FAILURE_ISSUE_URL:-}")"
existing_failure_issue_title="$(fallback "${EXISTING_FAILURE_ISSUE_TITLE:-}")"
superproject_repo_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}"
commit_link="n/a"
if [[ "${PUSHED:-}" == "true" && -n "${COMMIT_SHA:-}" ]]; then
commit_link="${superproject_repo_url}/commit/${COMMIT_SHA}"
fi
compare_link="n/a"
if [[ -n "${SUBMODULE_REPO_URL:-}" && -n "${CURRENT_SUBMODULE_COMMIT:-}" && -n "${LATEST_COMMIT:-}" && "${CURRENT_SUBMODULE_COMMIT}" != "${LATEST_COMMIT}" ]]; then
compare_link="${SUBMODULE_REPO_URL}/compare/${CURRENT_SUBMODULE_COMMIT}...${LATEST_COMMIT}"
fi
status_line="ℹ️ No update needed"
if [[ "${SHOULD_BLOCK:-}" == "true" ]]; then
status_line="⏭️ Skipped due to open failure issue"
elif [[ "${UPDATE_OUTCOME:-}" == "failure" || "${CI_CHECKS_OUTCOME:-}" == "failure" || "${E2E_CHECKS_OUTCOME:-}" == "failure" || "${VERIFY_OCI_OUTCOME:-}" == "failure" || "${COMMIT_PUSH_OUTCOME:-}" == "failure" || "${UPDATE_OUTCOME:-}" == "cancelled" || "${CI_CHECKS_OUTCOME:-}" == "cancelled" || "${E2E_CHECKS_OUTCOME:-}" == "cancelled" || "${VERIFY_OCI_OUTCOME:-}" == "cancelled" || "${COMMIT_PUSH_OUTCOME:-}" == "cancelled" ]]; then
status_line="❌ Failed"
elif [[ "${PUSHED:-}" == "true" ]]; then
status_line="✅ Updated and pushed"
fi
{
echo "## ${status_line}"
echo ""
echo "**Trigger:** \`${GITHUB_EVENT_NAME}\`"
echo "**Ref:** \`${GITHUB_REF}\`"
echo "**Actor:** \`${GITHUB_ACTOR}\`"
echo ""
echo "**Submodule branch:** \`${branch}\`"
echo "**Submodule repo:** ${repo_url}"
echo ""
echo "**Submodule commit:** \`${current_submodule} -> ${latest_submodule}\`"
echo "**Dockerfile pin:** \`${current_pin} -> ${updated_pin}\`"
echo ""
echo "**Update flags:**"
echo "- has_open_failure_issue: \`${has_open_failure_issue}\`"
echo "- should_block: \`${should_block}\`"
echo "- needs_update: \`${needs_update}\`"
echo "- updated: \`${updated}\`"
echo "- changed: \`${changed}\`"
echo "- pushed: \`${pushed}\`"
echo ""
echo "**Links:**"
if [[ "${compare_link}" == "n/a" ]]; then
echo "- submodule compare: n/a"
else
echo "- submodule compare: ${compare_link}"
fi
if [[ "${commit_link}" == "n/a" ]]; then
echo "- pushed superproject commit: n/a"
else
echo "- pushed superproject commit: ${commit_link}"
fi
if [[ "${existing_failure_issue_url}" == "n/a" ]]; then
echo "- open failure issue: n/a"
elif [[ "${existing_failure_issue_number}" == "n/a" ]]; then
echo "- open failure issue: [${existing_failure_issue_title}](${existing_failure_issue_url})"
else
echo "- open failure issue: [#${existing_failure_issue_number} ${existing_failure_issue_title}](${existing_failure_issue_url})"
fi
echo ""
echo "**Step outcomes:**"
echo "- failure_issue_gate: \`${FAILURE_ISSUE_GATE_OUTCOME:-n/a}\`"
echo "- update: \`${UPDATE_OUTCOME:-n/a}\`"
echo "- ci_checks: \`${CI_CHECKS_OUTCOME:-n/a}\`"
echo "- e2e_checks: \`${E2E_CHECKS_OUTCOME:-n/a}\`"
echo "- verify_oci: \`${VERIFY_OCI_OUTCOME:-n/a}\`"
echo "- commit_push: \`${COMMIT_PUSH_OUTCOME:-n/a}\`"
} >> "${GITHUB_STEP_SUMMARY}"
- name: Create issue on CI failure
if: failure() && (steps.ci_checks.outcome == 'failure' || steps.e2e_checks.outcome == 'failure') && steps.failure_issue_gate.outputs.has_open_failure_issue != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
LATEST_COMMIT: ${{ steps.update.outputs.latest_commit }}
SUBMODULE_REPO_URL: ${{ steps.update.outputs.submodule_repo_url }}
SUBMODULE_BRANCH: ${{ steps.update.outputs.submodule_branch }}
run: |
set -euo pipefail
# Ensure required labels exist (--force is a no-op if label already exists)
gh label create "bug" --description "Something isn't working" --color "d73a4a" --force || true
gh label create "automated" --description "Created by automation" --color "0e8a16" --force || true
run_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
commit_short="${LATEST_COMMIT:0:12}"
commit_url="${SUBMODULE_REPO_URL:-}/commit/${LATEST_COMMIT:-unknown}"
branch="${SUBMODULE_BRANCH:-dev}"
body="## Automated submodule update failed CI/e2e checks
The [update-opencode-commit workflow](${run_url}) attempted to update the
opencode submodule to [\`${commit_short}\`](${commit_url}) from the
\`${branch}\` branch, but checks failed after auto-formatting.
### What to do
1. Check the [workflow run logs](${run_url}) for the specific failure
2. Common causes:
- **TypeScript typecheck failure** in a fork-* package — fix in the opencode fork and push to \`dev\`
- **Rust clippy/build failure** in opencode-broker — fix in the fork
- **Formatting issue** that \`just fmt\` couldn't resolve — manual fix needed
- **E2E test failure** — UI regression in the web app, check Playwright report
3. After fixing, the next scheduled run (every 2 hours) will retry automatically
### Context
- Submodule commit: [\`${commit_short}\`](${commit_url})
- Fork branch: \`${branch}\`
- Workflow run: [${GITHUB_RUN_ID}](${run_url})"
gh issue create \
--title "CI checks failed for opencode submodule update (${commit_short})" \
--label "bug,automated" \
--body "${body}"