From d80e127cd3040605ace2f1cb5f9a831d068f933f Mon Sep 17 00:00:00 2001 From: zhom <2717306+zhom@users.noreply.github.com> Date: Sat, 8 Aug 2026 22:29:01 +0400 Subject: [PATCH] chore: switch to ai-inference v3 and fail workflows on 410 --- .github/prompts/release-notes.prompt.yml | 7 ++- .../telegram-release-summary.prompt.yml | 7 ++- .github/workflows/issue-compliance.yml | 19 +++++++- .github/workflows/issue-validation.yml | 46 ++++++++++++++++--- .github/workflows/notify-telegram.yml | 16 +++++-- .github/workflows/pr-ai-policy.yml | 19 +++++++- .github/workflows/release-notes-generator.yml | 16 +++++-- 7 files changed, 112 insertions(+), 18 deletions(-) diff --git a/.github/prompts/release-notes.prompt.yml b/.github/prompts/release-notes.prompt.yml index 36d3b2e..1eb38b2 100644 --- a/.github/prompts/release-notes.prompt.yml +++ b/.github/prompts/release-notes.prompt.yml @@ -30,4 +30,9 @@ messages: ### Documentation ### Dependencies ### Developer Experience -model: openai/gpt-4.1 +# `auto` lets the Copilot CLI pick. Deliberately not a pinned model id: it is +# the only value valid on every Copilot plan (Free and Student get auto +# selection only), and it cannot go stale the way `openai/gpt-4.1` did when +# GitHub Models was retired on 2026-07-30 and took both of these workflows +# down with it. +model: auto diff --git a/.github/prompts/telegram-release-summary.prompt.yml b/.github/prompts/telegram-release-summary.prompt.yml index ca7f40f..8295ec1 100644 --- a/.github/prompts/telegram-release-summary.prompt.yml +++ b/.github/prompts/telegram-release-summary.prompt.yml @@ -20,4 +20,9 @@ messages: {{commits}} Format: one short opening sentence, a blank line, then bullets starting with "- " (one per line). Nothing else. -model: openai/gpt-4.1 +# `auto` lets the Copilot CLI pick. Deliberately not a pinned model id: it is +# the only value valid on every Copilot plan (Free and Student get auto +# selection only), and it cannot go stale the way `openai/gpt-4.1` did when +# GitHub Models was retired on 2026-07-30 and took both of these workflows +# down with it. +model: auto diff --git a/.github/workflows/issue-compliance.yml b/.github/workflows/issue-compliance.yml index a090c91..6388e9d 100644 --- a/.github/workflows/issue-compliance.yml +++ b/.github/workflows/issue-compliance.yml @@ -96,7 +96,8 @@ jobs: -d "$PAYLOAD" || echo "000") if [ "$STATUS" != "200" ]; then - echo "::warning::GitHub Models returned HTTP $STATUS; treating as compliant" + echo "::error::GitHub Models returned HTTP $STATUS; treating as compliant" + printf '%s\n' "inference call failed with HTTP $STATUS" >> /tmp/ai-degraded echo '{"is_compliant": true, "non_compliance_reasons": []}' > /tmp/result.json exit 0 fi @@ -107,7 +108,8 @@ jobs: # to a compliant result so a flaky model never closes a legitimate issue. sed -E 's/^```(json)?$//; s/```$//' /tmp/raw.txt > /tmp/result.json if ! jq -e . /tmp/result.json >/dev/null 2>&1; then - echo "::warning::Model returned non-JSON; treating as compliant" + echo "::error::Model returned non-JSON; treating as compliant" + printf '%s\n' "model returned output that was not JSON" >> /tmp/ai-degraded echo '{"is_compliant": true, "non_compliance_reasons": []}' > /tmp/result.json fi echo "Compliance response validated" @@ -145,3 +147,16 @@ jobs: run: | gh issue comment "$ISSUE_NUMBER" --repo "$GITHUB_REPOSITORY" --body-file /tmp/comment.md gh issue close "$ISSUE_NUMBER" --repo "$GITHUB_REPOSITORY" --reason "not planned" + + # The steps above deliberately degrade rather than block: an inference + # outage must never close a contributor's issue or flag their pull + # request. But a run that skipped the check it exists to perform has not + # succeeded, and reporting green hides that the automation is dead. + - name: Fail if the AI check did not actually run + if: always() + run: | + if [ -f /tmp/ai-degraded ]; then + echo "::error::This check degraded to a no-op and its result was not verified:" + sed 's/^/ - /' /tmp/ai-degraded + exit 1 + fi diff --git a/.github/workflows/issue-validation.yml b/.github/workflows/issue-validation.yml index 8cbaded..c91191b 100644 --- a/.github/workflows/issue-validation.yml +++ b/.github/workflows/issue-validation.yml @@ -257,7 +257,8 @@ jobs: if [ "$STATUS" = "200" ]; then jq -r '.choices[0].message.content // empty' /tmp/triage-response.json > /tmp/triage-raw.txt || : > /tmp/triage-raw.txt else - echo "::warning::GitHub Models returned HTTP $STATUS for triage" + echo "::error::GitHub Models returned HTTP $STATUS for triage" + printf '%s\n' "triage inference call failed with HTTP $STATUS" >> /tmp/ai-degraded : > /tmp/triage-raw.txt fi @@ -266,7 +267,8 @@ jobs: # Fall back to a safe classification when the response is not JSON. if ! jq -e . /tmp/triage.json >/dev/null 2>&1; then - echo "::warning::Triage returned non-JSON; using fallback classification" + echo "::error::Triage returned non-JSON; using fallback classification" + printf '%s\n' "triage returned output that was not JSON" >> /tmp/ai-degraded jq -n '{ language: "en", classification: "bug-in-scope", @@ -436,7 +438,8 @@ jobs: -d "$PAYLOAD" || echo "000") if [ "$STATUS" != "200" ]; then - echo "::warning::GitHub Models returned HTTP $STATUS; skipping the triage comment" + echo "::error::GitHub Models returned HTTP $STATUS; skipping the triage comment" + printf '%s\n' "composer inference call failed with HTTP $STATUS" >> /tmp/ai-degraded echo "has_comment=false" >> "$GITHUB_OUTPUT" exit 0 fi @@ -444,7 +447,8 @@ jobs: jq -r '.choices[0].message.content // empty' /tmp/compose-response.json > /tmp/ai-comment.txt || : > /tmp/ai-comment.txt if [ ! -s /tmp/ai-comment.txt ]; then - echo "::warning::Composer returned empty response; skipping the triage comment" + echo "::error::Composer returned empty response; skipping the triage comment" + printf '%s\n' "composer returned an empty response" >> /tmp/ai-degraded echo "has_comment=false" >> "$GITHUB_OUTPUT" exit 0 fi @@ -482,6 +486,20 @@ jobs: run: | gh issue comment "$ISSUE_NUMBER" --repo "$GITHUB_REPOSITORY" --body-file /tmp/ai-comment.txt + + # The steps above deliberately degrade rather than block: an inference + # outage must never close a contributor's issue or flag their pull + # request. But a run that skipped the check it exists to perform has not + # succeeded, and reporting green hides that the automation is dead. + - name: Fail if the AI check did not actually run + if: always() + run: | + if [ -f /tmp/ai-degraded ]; then + echo "::error::This check degraded to a no-op and its result was not verified:" + sed 's/^/ - /' /tmp/ai-degraded + exit 1 + fi + analyze-pr: if: github.repository == 'zhom/donutbrowser' && github.event_name == 'pull_request_target' && github.actor != 'dependabot[bot]' runs-on: ubuntu-latest @@ -619,7 +637,8 @@ jobs: -d "$PAYLOAD" || echo "000") if [ "$STATUS" != "200" ]; then - echo "::warning::GitHub Models returned HTTP $STATUS; skipping the review comment" + echo "::error::GitHub Models returned HTTP $STATUS; skipping the review comment" + printf '%s\n' "PR review inference call failed with HTTP $STATUS" >> /tmp/ai-degraded echo "has_comment=false" >> "$GITHUB_OUTPUT" exit 0 fi @@ -627,7 +646,8 @@ jobs: jq -r '.choices[0].message.content // empty' /tmp/pr-response.json > /tmp/ai-comment.txt || : > /tmp/ai-comment.txt if [ ! -s /tmp/ai-comment.txt ]; then - echo "::warning::AI response was empty; skipping the review comment" + echo "::error::AI response was empty; skipping the review comment" + printf '%s\n' "PR review returned an empty response" >> /tmp/ai-degraded echo "has_comment=false" >> "$GITHUB_OUTPUT" exit 0 fi @@ -642,6 +662,20 @@ jobs: run: | gh pr comment "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --body-file /tmp/ai-comment.txt + + # The steps above deliberately degrade rather than block: an inference + # outage must never close a contributor's issue or flag their pull + # request. But a run that skipped the check it exists to perform has not + # succeeded, and reporting green hides that the automation is dead. + - name: Fail if the AI check did not actually run + if: always() + run: | + if [ -f /tmp/ai-degraded ]; then + echo "::error::This check degraded to a no-op and its result was not verified:" + sed 's/^/ - /' /tmp/ai-degraded + exit 1 + fi + opencode-command: if: | github.repository == 'zhom/donutbrowser' && diff --git a/.github/workflows/notify-telegram.yml b/.github/workflows/notify-telegram.yml index ed4755c..619cc5c 100644 --- a/.github/workflows/notify-telegram.yml +++ b/.github/workflows/notify-telegram.yml @@ -22,7 +22,7 @@ on: permissions: contents: read - models: read + copilot-requests: write jobs: notify: @@ -123,17 +123,27 @@ jobs: echo "previous-tag=${PREV_TAG}" >> "$GITHUB_OUTPUT" echo "Collected $(wc -l < commits.txt) commits between ${PREV_TAG} and ${TAG}." + # The Copilot CLI is not preinstalled on GitHub-hosted runners, and + # ai-inference v3 shells out to it. + - name: Install Copilot CLI + if: steps.gate.outputs.skip != 'true' + run: npm install -g @github/copilot + - name: Generate summary with AI id: ai if: steps.gate.outputs.skip != 'true' - uses: actions/ai-inference@a7805884c80886efc241e94a5351df715968a0ad # v2.1.1 + uses: actions/ai-inference@2c43c91ae16266ca159d311430343c67a5ffa222 # v3 with: prompt-file: .github/prompts/telegram-release-summary.prompt.yml input: | version: ${{ steps.tag.outputs.tag }} file_input: | commits: ./commits.txt - max-tokens: 1024 + env: + # The Copilot CLI reads its credential from the environment; the + # workflow token carries it under the `copilot-requests` permission + # granted above, so no PAT is needed. + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Post release announcement to Telegram if: steps.gate.outputs.skip != 'true' diff --git a/.github/workflows/pr-ai-policy.yml b/.github/workflows/pr-ai-policy.yml index a0d19cf..0a69737 100644 --- a/.github/workflows/pr-ai-policy.yml +++ b/.github/workflows/pr-ai-policy.yml @@ -134,7 +134,8 @@ jobs: -d "$PAYLOAD" || echo "000") if [ "$STATUS" != "200" ]; then - echo "::warning::GitHub Models returned HTTP $STATUS; treating as compliant" + echo "::error::GitHub Models returned HTTP $STATUS; treating as compliant" + printf '%s\n' "inference call failed with HTTP $STATUS" >> /tmp/ai-degraded echo '{"compliant": true, "violations": []}' > /tmp/result.json exit 0 fi @@ -146,7 +147,8 @@ jobs: # The deterministic trailer scan still stands on its own below. sed -E 's/^```(json)?$//; s/```$//' /tmp/raw.txt > /tmp/result.json if ! jq -e . /tmp/result.json >/dev/null 2>&1; then - echo "::warning::Model returned non-JSON; treating as compliant" + echo "::error::Model returned non-JSON; treating as compliant" + printf '%s\n' "model returned output that was not JSON" >> /tmp/ai-degraded echo '{"compliant": true, "violations": []}' > /tmp/result.json fi echo "Policy response validated" @@ -205,3 +207,16 @@ jobs: run: | gh pr comment "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --body-file /tmp/comment.md gh pr close "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" + + # The steps above deliberately degrade rather than block: an inference + # outage must never close a contributor's issue or flag their pull + # request. But a run that skipped the check it exists to perform has not + # succeeded, and reporting green hides that the automation is dead. + - name: Fail if the AI check did not actually run + if: always() + run: | + if [ -f /tmp/ai-degraded ]; then + echo "::error::This check degraded to a no-op and its result was not verified:" + sed 's/^/ - /' /tmp/ai-degraded + exit 1 + fi diff --git a/.github/workflows/release-notes-generator.yml b/.github/workflows/release-notes-generator.yml index 24dc58c..0a522f6 100644 --- a/.github/workflows/release-notes-generator.yml +++ b/.github/workflows/release-notes-generator.yml @@ -8,7 +8,7 @@ on: permissions: contents: write - models: read + copilot-requests: write jobs: generate-release-notes: @@ -79,17 +79,27 @@ jobs: echo "commits-file=commits.txt" >> $GITHUB_OUTPUT echo "changes-file=changes.txt" >> $GITHUB_OUTPUT + # The Copilot CLI is not preinstalled on GitHub-hosted runners, and + # ai-inference v3 shells out to it. + - name: Install Copilot CLI + if: steps.get-release.outputs.is-prerelease == 'false' + run: npm install -g @github/copilot + - name: Generate release notes with AI id: generate-notes if: steps.get-release.outputs.is-prerelease == 'false' - uses: actions/ai-inference@a7805884c80886efc241e94a5351df715968a0ad # v2.1.1 + uses: actions/ai-inference@2c43c91ae16266ca159d311430343c67a5ffa222 # v3 with: prompt-file: .github/prompts/release-notes.prompt.yml input: | version: ${{ steps.get-previous-tag.outputs.current-tag }} file_input: | commits: ./commits.txt - max-tokens: 4096 + env: + # The Copilot CLI reads its credential from the environment; the + # workflow token carries it under the `copilot-requests` permission + # granted above, so no PAT is needed. + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Update release with generated notes if: steps.get-release.outputs.is-prerelease == 'false'