fix(.github/workflows): send a real newline in the Slack alert, retry… #2
Workflow file for this run
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
| # Refreshes the AI Gateway price book from live upstream data (models.dev) | |
| # once a week and opens a pull request when the generated artifacts change. | |
| # | |
| # The price book seeds customer-visible cost numbers, so the refresh is never | |
| # merged automatically. The workflow only ever proposes a change; a human | |
| # reviews and merges it. | |
| # | |
| # Behavior: | |
| # - Runs every Monday. If regeneration produces no diff, the run ends | |
| # without opening anything. | |
| # - Reuses a single branch and pull request, force-pushing each week, so at | |
| # most one refresh PR is open and it always carries the newest snapshot. | |
| # - Fails loudly when the generator refuses to run, which it does by design | |
| # when upstream drops a model pinned in overrides.jq or curated in | |
| # curation.json. Failures are announced in Slack. | |
| name: aigateway-prices-refresh | |
| on: | |
| schedule: | |
| # 09:00 UTC every Monday, so the PR is waiting when EU and US start the week. | |
| - cron: "0 9 * * 1" | |
| workflow_dispatch: # allows manual runs for testing | |
| # TEMPORARY: schedule and workflow_dispatch only fire from the default | |
| # branch, so this trigger is the only way to exercise the workflow before | |
| # it merges. Drop this commit once the run has been verified. | |
| push: | |
| branches: | |
| - yevhenii/aigov-578-automate-updates-to-the-shipped-ai-model-price-book | |
| permissions: {} | |
| concurrency: | |
| group: aigateway-prices-refresh | |
| env: | |
| REFRESH_BRANCH: bot/aigateway-prices-refresh | |
| PRICES_FILE: coderd/aibridge/prices/data/prices.json | |
| CATALOG_FILE: site/src/pages/AgentsPage/components/ChatModelAdminPanel/knownModels/knownModelsGenerated.json | |
| jobs: | |
| refresh: | |
| name: Refresh price book | |
| runs-on: ubuntu-latest | |
| permissions: {} | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Set up mise tools | |
| uses: ./.github/actions/setup-mise | |
| with: | |
| install-args: "go node pnpm" | |
| # Needed by catalog generation, which formats its output with biome. | |
| - name: Install pnpm dependencies | |
| uses: ./.github/actions/pnpm-install | |
| - name: Snapshot the current price book | |
| run: cp "${PRICES_FILE}" "${RUNNER_TEMP}/prices-before.json" | |
| - name: Regenerate price book and model catalog | |
| run: make gen/aibridge-prices | |
| - name: Detect changes | |
| id: detect | |
| run: | | |
| set -euo pipefail | |
| if git diff --quiet -- "${PRICES_FILE}" "${CATALOG_FILE}"; then | |
| # exit 0 => NO differences => nothing to propose | |
| echo "Price book already matches upstream; nothing to propose." | |
| echo "changed=false" >> "$GITHUB_OUTPUT" | |
| else | |
| # exit 1 => differences found | |
| git diff --stat -- "${PRICES_FILE}" "${CATALOG_FILE}" | |
| echo "changed=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Build pull request body | |
| if: steps.detect.outputs.changed == 'true' | |
| run: | | |
| set -euo pipefail | |
| go run ./scripts/aibridgepricesdiff \ | |
| -old "${RUNNER_TEMP}/prices-before.json" \ | |
| -new "${PRICES_FILE}" > "${RUNNER_TEMP}/summary.md" | |
| { | |
| cat "${RUNNER_TEMP}/summary.md" | |
| echo | |
| echo "## Review notes" | |
| echo | |
| echo "Regenerated by \`make gen/aibridge-prices\` from the live" | |
| echo "[models.dev](https://models.dev) catalog. Both artifacts come from one" | |
| echo "snapshot, so they ship together:" | |
| echo | |
| echo "- \`${PRICES_FILE}\`" | |
| echo "- \`${CATALOG_FILE}\`" | |
| echo | |
| echo "These are customer-visible cost numbers taken from upstream data, so this" | |
| echo "PR is never merged automatically. The summary above lists what moved; check" | |
| echo "the diff for exact figures before approving." | |
| echo | |
| echo "Opened automatically by the [aigateway-prices-refresh workflow](${RUN_URL})." | |
| } > "${RUNNER_TEMP}/body.md" | |
| cat "${RUNNER_TEMP}/body.md" | |
| env: | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| - name: Open or update the refresh pull request | |
| if: steps.detect.outputs.changed == 'true' | |
| env: | |
| # Use cdrci's token instead of the default GITHUB_TOKEN: PRs opened | |
| # with GITHUB_TOKEN do not trigger workflow runs, so the refresh | |
| # would arrive without CI signal. | |
| GH_TOKEN: ${{ secrets.CDRCI_GITHUB_TOKEN }} | |
| PR_TITLE: "chore: refresh AI model price book" | |
| run: | | |
| set -euo pipefail | |
| # persist-credentials is disabled on checkout, so authenticate the | |
| # push explicitly. | |
| git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git checkout -b "${REFRESH_BRANCH}" | |
| git add -- "${PRICES_FILE}" "${CATALOG_FILE}" | |
| git commit -m "${PR_TITLE}" | |
| # Force-push: the branch is regenerated from the newest upstream | |
| # snapshot each week, so the previous contents are always stale. | |
| git push --force origin "refs/heads/${REFRESH_BRANCH}" | |
| # The GitHub API returns transient 5xx errors; without a retry a | |
| # blip leaves the branch pushed and no pull request until the next | |
| # weekly run. | |
| pr_number="$(./.github/scripts/retry.sh -- gh pr list --head "${REFRESH_BRANCH}" --base main --state open --json number --jq '.[0].number // empty')" | |
| if [ -n "${pr_number}" ]; then | |
| ./.github/scripts/retry.sh -- gh pr edit "${pr_number}" --title "${PR_TITLE}" --body-file "${RUNNER_TEMP}/body.md" | |
| echo "Updated existing PR #${pr_number}." | |
| else | |
| ./.github/scripts/retry.sh -- gh pr create \ | |
| --base main \ | |
| --head "${REFRESH_BRANCH}" \ | |
| --title "${PR_TITLE}" \ | |
| --body-file "${RUNNER_TEMP}/body.md" | |
| fi | |
| - name: Send Slack notification on failure | |
| if: failure() | |
| env: | |
| SLACK_WEBHOOK: ${{ secrets.AIGATEWAY_PRICES_SLACK_WEBHOOK }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${SLACK_WEBHOOK}" ]; then | |
| echo "::warning::AIGATEWAY_PRICES_SLACK_WEBHOOK is not set; skipping notification." | |
| exit 0 | |
| fi | |
| # printf, not a double-quoted literal: bash leaves \n as two | |
| # characters, and jq --arg then escapes the backslash, so Slack | |
| # would print \n as text instead of breaking the line. | |
| text="$(printf ':warning: *AI model price book refresh failed.*\nThe generator fails by design when upstream drops a model pinned in scripts/aibridgepricesgen/overrides.jq or curated in curation.json. Logs: %s' "${RUN_URL}")" | |
| payload="$(jq -nc --arg text "${text}" '{text: $text}')" | |
| curl -fsSL -X POST -H 'Content-type: application/json' -d "${payload}" "${SLACK_WEBHOOK}" | |
| echo "Sent Slack notification" |