1919 with :
2020 token : ${{ secrets.GITHUB_TOKEN }}
2121
22+ - name : Check for existing automated submodule failure issue
23+ id : failure_issue_gate
24+ uses : actions/github-script@v7
25+ with :
26+ script : |
27+ const failureMarker = 'Automated submodule update failed';
28+ const openAutomatedIssues = await github.paginate(
29+ github.rest.issues.listForRepo,
30+ {
31+ owner: context.repo.owner,
32+ repo: context.repo.repo,
33+ state: 'open',
34+ labels: 'automated',
35+ per_page: 100,
36+ },
37+ );
38+
39+ const matchingIssues = openAutomatedIssues.filter((issue) =>
40+ (issue.body || '').includes(failureMarker),
41+ );
42+
43+ matchingIssues.sort((a, b) => b.number - a.number);
44+ const existingIssue = matchingIssues[0] || null;
45+ const hasOpenFailureIssue = Boolean(existingIssue);
46+ const shouldBlock =
47+ context.eventName === 'schedule' && hasOpenFailureIssue;
48+
49+ core.info(
50+ `failure_issue_gate: event=${context.eventName}, has_open_failure_issue=${hasOpenFailureIssue}, should_block=${shouldBlock}`,
51+ );
52+ if (existingIssue) {
53+ core.info(
54+ `failure_issue_gate: using #${existingIssue.number} ${existingIssue.html_url}`,
55+ );
56+ }
57+
58+ core.setOutput('has_open_failure_issue', String(hasOpenFailureIssue));
59+ core.setOutput(
60+ 'existing_failure_issue_number',
61+ existingIssue ? String(existingIssue.number) : '',
62+ );
63+ core.setOutput(
64+ 'existing_failure_issue_url',
65+ existingIssue ? existingIssue.html_url : '',
66+ );
67+ core.setOutput(
68+ 'existing_failure_issue_title',
69+ existingIssue ? existingIssue.title : '',
70+ );
71+ core.setOutput('should_block', String(shouldBlock));
72+
2273 - name : Initialize opencode submodule
74+ if : steps.failure_issue_gate.outputs.should_block != 'true'
2375 run : |
2476 # .gitmodules tracks opencode with an SSH URL. Actions runners
2577 # typically lack SSH deploy keys, so rewrite to HTTPS inline.
@@ -29,39 +81,47 @@ jobs:
2981 git submodule status --recursive
3082
3183 - name : Configure Git
84+ if : steps.failure_issue_gate.outputs.should_block != 'true'
3285 run : |
3386 git config user.name "github-actions[bot]"
3487 git config user.email "github-actions[bot]@users.noreply.github.com"
3588
3689 # Toolchain setup for running CI checks on the updated submodule.
3790 # Steps match ci-pre-publish.yml to ensure parity with normal CI.
3891 - name : Install Linux system dependencies
92+ if : steps.failure_issue_gate.outputs.should_block != 'true'
3993 run : |
4094 # Required by opencode-broker (libpam-sys links against -lpam).
4195 sudo apt-get update
4296 sudo apt-get install -y libpam0g-dev
4397
4498 - name : Install Rust
99+ if : steps.failure_issue_gate.outputs.should_block != 'true'
45100 uses : dtolnay/rust-toolchain@stable
46101 with :
47102 components : rustfmt, clippy
48103
49104 - name : Install just
105+ if : steps.failure_issue_gate.outputs.should_block != 'true'
50106 uses : extractions/setup-just@v3
51107
52108 - name : Setup Node.js
109+ if : steps.failure_issue_gate.outputs.should_block != 'true'
53110 uses : actions/setup-node@v5
54111 with :
55112 node-version : ' 20'
56113
57114 - name : Setup Bun
115+ if : steps.failure_issue_gate.outputs.should_block != 'true'
58116 uses : ./.github/actions/setup-bun
59117
60118 - name : Rust cache
119+ if : steps.failure_issue_gate.outputs.should_block != 'true'
61120 uses : Swatinem/rust-cache@v2
62121
63122 - name : Update opencode commit
64123 id : update
124+ if : steps.failure_issue_gate.outputs.should_block != 'true'
65125 run : |
66126 ./scripts/update-opencode-commit.sh
67127
70130 # If checks fail, the workflow fails and the broken commit is never pushed.
71131 - name : Auto-format and run CI checks
72132 id : ci_checks
73- if : steps.update.outputs.needs_update == 'true'
133+ if : steps.failure_issue_gate.outputs.should_block != 'true' && steps. update.outputs.needs_update == 'true'
74134 run : |
75135 set -euo pipefail
76136
@@ -88,16 +148,18 @@ jobs:
88148
89149 - name : Run e2e tests
90150 id : e2e_checks
91- if : steps.update.outputs.needs_update == 'true'
151+ if : steps.failure_issue_gate.outputs.should_block != 'true' && steps. update.outputs.needs_update == 'true'
92152 run : just ci-e2e
93153 timeout-minutes : 30
94154
95155 - name : Verify OCI description label
96156 id : verify_oci
157+ if : steps.failure_issue_gate.outputs.should_block != 'true'
97158 run : python3 scripts/extract-oci-description.py packages/core/src/docker/Dockerfile
98159
99160 - name : Commit and push if changed
100161 id : commit_push
162+ if : steps.failure_issue_gate.outputs.should_block != 'true'
101163 run : |
102164 if git diff --quiet; then
103165 echo "No changes to commit."
@@ -127,9 +189,25 @@ jobs:
127189 git push
128190 echo "pushed=true" >> "$GITHUB_OUTPUT"
129191
192+ - name : Notice skipped update due to existing open failure issue
193+ if : steps.failure_issue_gate.outputs.should_block == 'true'
194+ env :
195+ EXISTING_FAILURE_ISSUE_NUMBER : ${{ steps.failure_issue_gate.outputs.existing_failure_issue_number }}
196+ EXISTING_FAILURE_ISSUE_URL : ${{ steps.failure_issue_gate.outputs.existing_failure_issue_url }}
197+ EXISTING_FAILURE_ISSUE_TITLE : ${{ steps.failure_issue_gate.outputs.existing_failure_issue_title }}
198+ run : |
199+ set -euo pipefail
200+ 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})"
201+
130202 - name : Write summary
131203 if : always()
132204 env :
205+ FAILURE_ISSUE_GATE_OUTCOME : ${{ steps.failure_issue_gate.outcome }}
206+ HAS_OPEN_FAILURE_ISSUE : ${{ steps.failure_issue_gate.outputs.has_open_failure_issue }}
207+ EXISTING_FAILURE_ISSUE_NUMBER : ${{ steps.failure_issue_gate.outputs.existing_failure_issue_number }}
208+ EXISTING_FAILURE_ISSUE_URL : ${{ steps.failure_issue_gate.outputs.existing_failure_issue_url }}
209+ EXISTING_FAILURE_ISSUE_TITLE : ${{ steps.failure_issue_gate.outputs.existing_failure_issue_title }}
210+ SHOULD_BLOCK : ${{ steps.failure_issue_gate.outputs.should_block }}
133211 UPDATE_OUTCOME : ${{ steps.update.outcome }}
134212 CI_CHECKS_OUTCOME : ${{ steps.ci_checks.outcome }}
135213 E2E_CHECKS_OUTCOME : ${{ steps.e2e_checks.outcome }}
@@ -168,6 +246,11 @@ jobs:
168246 updated="$(fallback "${UPDATED:-}")"
169247 changed="$(fallback "${CHANGED:-}")"
170248 pushed="$(fallback "${PUSHED:-}")"
249+ has_open_failure_issue="$(fallback "${HAS_OPEN_FAILURE_ISSUE:-}")"
250+ should_block="$(fallback "${SHOULD_BLOCK:-}")"
251+ existing_failure_issue_number="$(fallback "${EXISTING_FAILURE_ISSUE_NUMBER:-}")"
252+ existing_failure_issue_url="$(fallback "${EXISTING_FAILURE_ISSUE_URL:-}")"
253+ existing_failure_issue_title="$(fallback "${EXISTING_FAILURE_ISSUE_TITLE:-}")"
171254
172255 superproject_repo_url="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}"
173256 commit_link="n/a"
@@ -181,7 +264,9 @@ jobs:
181264 fi
182265
183266 status_line="ℹ️ No update needed"
184- if [[ "${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
267+ if [[ "${SHOULD_BLOCK:-}" == "true" ]]; then
268+ status_line="⏭️ Skipped due to open failure issue"
269+ 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
185270 status_line="❌ Failed"
186271 elif [[ "${PUSHED:-}" == "true" ]]; then
187272 status_line="✅ Updated and pushed"
@@ -201,6 +286,8 @@ jobs:
201286 echo "**Dockerfile pin:** \`${current_pin} -> ${updated_pin}\`"
202287 echo ""
203288 echo "**Update flags:**"
289+ echo "- has_open_failure_issue: \`${has_open_failure_issue}\`"
290+ echo "- should_block: \`${should_block}\`"
204291 echo "- needs_update: \`${needs_update}\`"
205292 echo "- updated: \`${updated}\`"
206293 echo "- changed: \`${changed}\`"
@@ -217,8 +304,16 @@ jobs:
217304 else
218305 echo "- pushed superproject commit: ${commit_link}"
219306 fi
307+ if [[ "${existing_failure_issue_url}" == "n/a" ]]; then
308+ echo "- open failure issue: n/a"
309+ elif [[ "${existing_failure_issue_number}" == "n/a" ]]; then
310+ echo "- open failure issue: [${existing_failure_issue_title}](${existing_failure_issue_url})"
311+ else
312+ echo "- open failure issue: [#${existing_failure_issue_number} ${existing_failure_issue_title}](${existing_failure_issue_url})"
313+ fi
220314 echo ""
221315 echo "**Step outcomes:**"
316+ echo "- failure_issue_gate: \`${FAILURE_ISSUE_GATE_OUTCOME:-n/a}\`"
222317 echo "- update: \`${UPDATE_OUTCOME:-n/a}\`"
223318 echo "- ci_checks: \`${CI_CHECKS_OUTCOME:-n/a}\`"
224319 echo "- e2e_checks: \`${E2E_CHECKS_OUTCOME:-n/a}\`"
@@ -227,7 +322,7 @@ jobs:
227322 } >> "${GITHUB_STEP_SUMMARY}"
228323
229324 - name : Create issue on CI failure
230- if : failure() && (steps.ci_checks.outcome == 'failure' || steps.e2e_checks.outcome == 'failure')
325+ if : failure() && (steps.ci_checks.outcome == 'failure' || steps.e2e_checks.outcome == 'failure') && steps.failure_issue_gate.outputs.has_open_failure_issue != 'true'
231326 env :
232327 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
233328 LATEST_COMMIT : ${{ steps.update.outputs.latest_commit }}
0 commit comments