chore(sdks): Version Packages #851
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
| name: PR Review | |
| # AI review using @uniswap/review-cli (private GitHub Packages). | |
| # | |
| # Replicates the setup running in Uniswap/universe and Uniswap/backend, | |
| # matched to internal-tools' canonical template | |
| # (packages/review-cli/templates/workflows/review.yml) in its 1.3.0+ | |
| # two-job shape: triage → review, with react/reply acknowledgements on | |
| # comment triggers. | |
| # | |
| # Documentation: https://www.notion.so/uniswaplabs/New-AI-Review-System-356c52b2548b80a1a615c9692312e517 | |
| # Support: #pod-apps-infra (Slack) — owners of the review-cli rollout. | |
| # | |
| # Job split: | |
| # triage — reads GITHUB_EVENT_PATH, decides run/skip, posts the 👀 | |
| # ack + "Reviewing now" threaded reply on comment triggers, | |
| # and exports outputs (pr_number, trigger_source, note, | |
| # commenter, thread_anchor, comment_id, reply_id) for review. | |
| # review — checks out the PR head, runs the multi-agent pipeline, | |
| # posts findings, then updates the ack reaction (✅/❌) and | |
| # edits the threaded reply with the terminal verdict. | |
| # | |
| # Verb separation: `review` analyzes and persists; `review post` pushes | |
| # the saved analysis to GitHub. Splitting the verbs means posting is | |
| # the only verb that writes — `review` itself is structurally | |
| # read-only (the AnalyzeDeps shape doesn't allow constructing a | |
| # writer). Posts/resolves review threads idempotently across | |
| # force-pushes; skips drafts; re-runs on every push. | |
| # | |
| # Triggers: | |
| # pull_request — automatic on opened/synchronize/reopened/ | |
| # ready_for_review (the common case). | |
| # issue_comment — fires when a PR comment contains | |
| # `@request-claude-review`. Lets developers | |
| # re-run the bot without pushing new commits. | |
| # pull_request_review_comment — same trigger phrase on inline review | |
| # comments. Anchors the steering note to a | |
| # specific file:line. | |
| # workflow_dispatch — manual fire from the Actions tab with a | |
| # `pr_number` input. | |
| # | |
| # Event parsing lives in the cli: `triage --from-github-actions` | |
| # resolves PR number + trigger source + steering note + commenter + | |
| # thread anchor from `GITHUB_EVENT_PATH`. The workflow never shell- | |
| # interpolates PR-author-controlled fields like `comment.body`; | |
| # everything rides through the event JSON straight into JS strings. | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened, ready_for_review] | |
| issue_comment: | |
| types: [created] | |
| pull_request_review_comment: | |
| types: [created] | |
| workflow_dispatch: | |
| inputs: | |
| pr_number: | |
| description: 'PR number to review' | |
| required: true | |
| type: string | |
| concurrency: | |
| # PR-number expression resolves at job-creation time, before any | |
| # step runs. Each event surface exposes the PR number on a | |
| # different path: | |
| # pull_request / pull_request_review_comment → event.pull_request.number | |
| # issue_comment → event.issue.number | |
| # workflow_dispatch → inputs.pr_number | |
| # The `||` chain picks the first non-empty value, so the | |
| # concurrency key is stable across all event sources. | |
| # | |
| # Comment events that DON'T contain the trigger phrase get a | |
| # per-run-unique group suffix so they can't cancel an in-flight | |
| # `pull_request` run that's actually doing work. Without this, | |
| # any `issue_comment` on a PR (bot chatter, generic discussion) | |
| # fires a sibling workflow run that grabs the `review-<N>` group, | |
| # cancels the in-flight review via `cancel-in-progress`, and then | |
| # immediately skips itself via the job-level `if:` below. | |
| # Concurrency is evaluated at run-creation time, BEFORE the job | |
| # `if:`, so the skip doesn't save us. | |
| group: >- | |
| review-${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number }}${{ | |
| (github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') | |
| && !contains(github.event.comment.body, '@request-claude-review') | |
| && format('-skip-{0}', github.run_id) | |
| || '' | |
| }} | |
| cancel-in-progress: true | |
| # Permissions are scoped per-job. Workflow-level permissions trip | |
| # zizmor's `excessive-permissions` rule — the triage job only needs | |
| # read + reaction-write; only the review job needs contents:write for | |
| # the resolveReviewThread GraphQL mutation. | |
| jobs: | |
| triage: | |
| name: Triage | |
| runs-on: ubuntu-latest | |
| # Coarse event-level gate — cheap early-exit so untrusted commenters | |
| # or unrelated comments don't even spin up the runner. The cli's | |
| # `triage --from-github-actions` step below is the authoritative | |
| # policy lookup (draft skip, association threshold, bot self-mention | |
| # guard, trigger-phrase parse); this `if:` just avoids paying the | |
| # runner-startup cost for events that obviously don't apply. | |
| if: | | |
| (github.event_name == 'pull_request' && !github.event.pull_request.draft) || | |
| (github.event_name == 'issue_comment' | |
| && github.event.issue.pull_request != null | |
| && contains(github.event.comment.body, '@request-claude-review') | |
| && github.event.comment.user.type != 'Bot' | |
| && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) || | |
| (github.event_name == 'pull_request_review_comment' | |
| && contains(github.event.comment.body, '@request-claude-review') | |
| && github.event.comment.user.type != 'Bot' | |
| && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) || | |
| github.event_name == 'workflow_dispatch' | |
| permissions: | |
| contents: read # checkout for the local install_review_cli action | |
| packages: read # read @uniswap/review-cli from GH packages | |
| issues: write # 👀 reaction on issue_comment | |
| pull-requests: write # 👀 reaction on pull_request_review_comment | |
| outputs: | |
| run: ${{ steps.gate.outputs.run }} | |
| pr_number: ${{ steps.gate.outputs.pr_number }} | |
| comment_id: ${{ steps.gate.outputs.comment_id }} | |
| trigger_source: ${{ steps.gate.outputs.trigger_source }} | |
| note: ${{ steps.gate.outputs.note }} | |
| commenter: ${{ steps.gate.outputs.commenter }} | |
| thread_anchor: ${{ steps.gate.outputs.thread_anchor }} | |
| # Threaded-reply id (when a running-state reply was posted in this | |
| # job). The Post step uses it to PATCH the reply with the terminal | |
| # verdict. Empty when the trigger wasn't a comment (push-driven | |
| # runs have no comment to reply to). | |
| reply_id: ${{ steps.post_reply.outputs.id }} | |
| steps: | |
| # Minimal checkout — the Install review-cli step below uses the | |
| # local composite action at .github/actions/install_review_cli, | |
| # so action.yml needs to be on disk. fetch-depth: 1 is enough | |
| # (no git history is consulted in triage). `persist-credentials: | |
| # false` because nothing in this job pushes or fetches from git. | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 1 | |
| persist-credentials: false | |
| - name: Install review-cli | |
| id: install-review-cli | |
| uses: ./.github/actions/install_review_cli | |
| with: | |
| # `vars.REVIEW_CLI_VERSION` is the authoritative pin; update | |
| # it in repo settings (Secrets and variables → Actions → | |
| # Variables) to roll forward without a commit. The fallback | |
| # below only matters if the variable is unset or deleted. | |
| # Must be >= 1.4.3: earlier versions leave a transitive | |
| # @modelcontextprotocol/server dependency unpinned, so a | |
| # fresh `bun add` resolves a newer build whose exports | |
| # changed and the CLI crashes at load ("Export named | |
| # 'StdioServerTransport' not found"). Never `@latest` — | |
| # would silently absorb upstream changes mid-PR. | |
| version: ${{ vars.REVIEW_CLI_VERSION || '1.6.0' }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| # Gate — `review-cli triage --from-github-actions` reads | |
| # GITHUB_EVENT_NAME + GITHUB_EVENT_PATH itself and writes the | |
| # decision to GITHUB_OUTPUT. No user-controlled data ever | |
| # touches the shell. `--skip-config` because we haven't checked | |
| # out the PR head yet; the cli's built-in defaults cover the | |
| # common cases. | |
| - name: Decide whether to run | |
| id: gate | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REVIEW_CLI_BIN: ${{ steps.install-review-cli.outputs.bin-path }} | |
| run: '"$REVIEW_CLI_BIN/review-cli" triage --from-github-actions --skip-config' | |
| # Acknowledge a comment trigger with 👀. Only fires when the | |
| # gate accepted the comment — bot-self-mentions, untrusted | |
| # commenters, and missing-trigger-phrase comments don't get the | |
| # ack (the comment id is empty in those cases). | |
| - name: Acknowledge comment trigger (👀) | |
| if: | | |
| steps.gate.outputs.run == 'true' && | |
| steps.gate.outputs.comment_id != '' | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| COMMENT_ID: ${{ steps.gate.outputs.comment_id }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| REVIEW_CLI_BIN: ${{ steps.install-review-cli.outputs.bin-path }} | |
| run: | | |
| "$REVIEW_CLI_BIN/review-cli" react \ | |
| --repo "$GH_REPO" \ | |
| --comment-id "$COMMENT_ID" \ | |
| --event "$EVENT_NAME" \ | |
| --reaction ack | |
| # Post a "Reviewing now" reply right under the trigger comment so | |
| # the requester gets visible feedback while the review job spins | |
| # up. The reaction (👀) is glanceable; this reply is the | |
| # responsive one — it carries the run URL so the requester can | |
| # click through to logs without scrolling back to the sticky. | |
| # Best-effort: a failed post doesn't break the run, the worst | |
| # case is silence until the terminal ✅/❌ reaction. | |
| - name: Post running-state reply | |
| id: post_reply | |
| if: | | |
| steps.gate.outputs.run == 'true' && | |
| steps.gate.outputs.comment_id != '' | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| PR_NUMBER: ${{ steps.gate.outputs.pr_number }} | |
| COMMENT_ID: ${{ steps.gate.outputs.comment_id }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| REPLY_BODY: | | |
| ↻ **Reviewing now** · [view run ↗](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) | |
| This comment will update when the review completes. Findings will appear in the sticky summary above. | |
| REVIEW_CLI_BIN: ${{ steps.install-review-cli.outputs.bin-path }} | |
| run: | | |
| OUT="$RUNNER_TEMP/reply-id.txt" | |
| "$REVIEW_CLI_BIN/review-cli" reply \ | |
| --repo "$GH_REPO" \ | |
| --pr "$PR_NUMBER" \ | |
| --event "$EVENT_NAME" \ | |
| --in-reply-to "$COMMENT_ID" \ | |
| --body "$REPLY_BODY" \ | |
| --out-id "$OUT" | |
| if [ -s "$OUT" ]; then | |
| echo "id=$(cat "$OUT")" >> "$GITHUB_OUTPUT" | |
| fi | |
| review: | |
| name: AI review | |
| needs: triage | |
| if: needs.triage.outputs.run == 'true' | |
| runs-on: ubuntu-latest | |
| # `contents: write` (not `read`) is required for the GraphQL | |
| # `resolveReviewThread` mutation — counterintuitive but documented. | |
| # `pull-requests: write` alone returns "Resource not accessible by | |
| # integration". https://github.com/orgs/community/discussions/44650 | |
| # | |
| # review-cli doesn't push to git (`persist-credentials: false` on | |
| # checkout). The elevated content scope is purely for the | |
| # review-thread resolve mutation. | |
| permissions: | |
| contents: write | |
| packages: read # pull @uniswap/review-cli | |
| pull-requests: write # post review comments + resolve threads | |
| issues: write # comment reactions + sticky on PRs | |
| # Hard cap: agent_budget_usd in .claude/review.yml is the soft cap; | |
| # this is the safety net if the CLI hangs. | |
| timeout-minutes: 20 | |
| env: | |
| # Exposes "is the DD key configured?" as a job-level env so the | |
| # optional DD CI Visibility step can gate on it. `secrets.*` can't | |
| # be referenced directly from a step-level `if:`, and step-level | |
| # `env:` isn't applied before `if:` is evaluated — but job-level | |
| # env IS, so this is the standard workaround. | |
| HAS_DATADOG_API_KEY: ${{ secrets.DD_API_KEY != '' }} | |
| steps: | |
| # Pin the checkout to the PR head. Required for comment-trigger | |
| # variants — `issue_comment` and `pull_request_review_comment` | |
| # events default GITHUB_REF to the repo's default branch, so an | |
| # un-pinned checkout would run the review against main and skip | |
| # on "all hunks identical". The triage job already extracted | |
| # pr_number from the event payload; reuse it here so the review | |
| # runs against the PR's actual code regardless of which event | |
| # triggered. | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| # Full history so review-cli can diff against the PR base. | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| ref: refs/pull/${{ needs.triage.outputs.pr_number }}/head | |
| # NOTE: deliberately not running the monorepo's own bun install. | |
| # The install_review_cli action below sets up Bun pinned to | |
| # review-cli's engines requirement and installs the cli into an | |
| # isolated $RUNNER_TEMP dir. A repo-wide install would be wasted | |
| # work, since review-cli reads the diff via `gh pr diff` and | |
| # never imports from the repo's node_modules. | |
| - name: Install review-cli | |
| id: install-review-cli | |
| uses: ./.github/actions/install_review_cli | |
| with: | |
| version: ${{ vars.REVIEW_CLI_VERSION || '1.6.0' }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| # Fail fast if the OAuth token is missing — without it | |
| # agent-query-factory falls back to a mock, which is confusing. | |
| # We deliberately only forward CLAUDE_CODE_OAUTH_TOKEN (subscription | |
| # auth, no metered billing) — never ANTHROPIC_API_KEY. | |
| - name: Verify Claude OAuth token present | |
| env: | |
| CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} | |
| run: | | |
| if [ -z "$CLAUDE_CODE_OAUTH_TOKEN" ]; then | |
| echo "::error::Set CLAUDE_CODE_OAUTH_TOKEN as a repo secret to enable AI review." | |
| exit 1 | |
| fi | |
| # Install the Claude Code native binary. | |
| # | |
| # As of @anthropic-ai/claude-agent-sdk@0.2.113 the SDK spawns a | |
| # per-platform native binary shipped via optional deps. Bun on Linux | |
| # installs the package metadata for every variant but only extracts the | |
| # tarball matching the host — and the SDK's resolver probes | |
| # `linux-<arch>-musl` first, so `require.resolve` returns a path to a | |
| # binary that doesn't exist on disk and the SDK errors out with | |
| # "Claude Code native binary not found at .../musl/claude". | |
| # | |
| # Sidestep the optional-dep dance by installing the binary via the | |
| # official script and pointing the SDK at it via | |
| # CLAUDE_CODE_EXECUTABLE_PATH (honored by review-cli). | |
| # | |
| # SECURITY: this is the one un-pinned external fetch in the | |
| # workflow. SHA-pinning `curl|bash` doesn't help here — the | |
| # installer is a per-host bootstrapper that selects a binary for | |
| # the runner's libc/arch at install time, so a fixed hash on a | |
| # multi-target script provides no integrity guarantee for the | |
| # binary that actually lands on disk. Pinning the hash of | |
| # `install.sh` itself isn't worth the operational cost either — | |
| # Anthropic rotates the bootstrapper independently of the binary | |
| # versions, so the hash would churn without correlating to | |
| # meaningful supply-chain events. Compensating controls: | |
| # - CLAUDE_CODE_OAUTH_TOKEN is a subscription-billed token — | |
| # not metered API spend — and is scoped to Claude Code only. | |
| # - We never forward ANTHROPIC_API_KEY to this job. | |
| # The asymmetry with the rest of the hash-pinned workflow is | |
| # intentional, not an oversight. | |
| - name: Install Claude Code binary | |
| run: | | |
| set -euo pipefail | |
| curl -fsSL https://claude.ai/install.sh | bash | |
| for candidate in "$HOME/.local/bin/claude" "$HOME/.claude/bin/claude" "$HOME/.npm-global/bin/claude"; do | |
| if [ -x "$candidate" ]; then | |
| CLAUDE_BIN="$candidate"; break | |
| fi | |
| done | |
| if [ -z "${CLAUDE_BIN:-}" ]; then | |
| CLAUDE_BIN="$(command -v claude || true)" | |
| fi | |
| if [ -z "$CLAUDE_BIN" ] || [ ! -x "$CLAUDE_BIN" ]; then | |
| echo "::error::claude binary not found after install"; exit 1 | |
| fi | |
| echo "Installed claude at: $CLAUDE_BIN" | |
| echo "CLAUDE_CODE_EXECUTABLE_PATH=$CLAUDE_BIN" >> "$GITHUB_ENV" | |
| echo "$(dirname "$CLAUDE_BIN")" >> "$GITHUB_PATH" | |
| # Pre — running sticky placeholder so the PR shows in-progress | |
| # state from t=0. Best-effort: if this fails Post creates the | |
| # sticky from scratch. | |
| - name: Pre — running sticky placeholder | |
| # Best-effort: a flaky GitHub API blip on the placeholder | |
| # upsert must not skip Analyze. Without `continue-on-error`, a | |
| # non-zero exit here would short-circuit the rest of the job | |
| # (default `if: success()` on subsequent steps), and Post | |
| # would have no `last-run.json` to recover from. | |
| continue-on-error: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| PR_NUMBER: ${{ needs.triage.outputs.pr_number }} | |
| REVIEW_CLI_BIN: ${{ steps.install-review-cli.outputs.bin-path }} | |
| run: '"$REVIEW_CLI_BIN/review-cli" post "$PR_NUMBER" --repo "$GH_REPO" --pre' | |
| # Analyze — reads the diff, runs the agents, persists | |
| # `~/.review-cli/<owner>/<repo>/pr-<n>/last-run.json`. Never | |
| # writes to GitHub — but it READS the diff via `gh pr diff`, | |
| # which needs GITHUB_TOKEN. Without it, gh fails silently, the | |
| # diff comes back empty, and the run short-circuits with | |
| # "PR has no changes". | |
| # | |
| # Trigger source + steering note + commenter + thread anchor | |
| # flow from the triage job's outputs via env vars (NOT shell- | |
| # interpolated) so a malicious comment body can't break out | |
| # into shell. The thread anchor is JSON-encoded. | |
| - name: Analyze | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} | |
| REVIEW_NOTE: ${{ needs.triage.outputs.note }} | |
| REVIEW_TRIGGER_SOURCE: ${{ needs.triage.outputs.trigger_source }} | |
| REVIEW_COMMENTER: ${{ needs.triage.outputs.commenter }} | |
| REVIEW_THREAD_ANCHOR: ${{ needs.triage.outputs.thread_anchor }} | |
| # Pin where the cli writes the run artifact so the Upload | |
| # and DD CI Stamp steps below can read it back from | |
| # `${{ runner.temp }}` (cli default is `./` which would | |
| # silently drop both downstream consumers). | |
| REVIEW_CLI_ARTIFACT_PATH: ${{ runner.temp }}/review-cli-output.json | |
| # Per-agent cost record stream — picked up by the | |
| # agent-scorecard aggregator (Uniswap/internal-tools) | |
| # alongside the existing review-feedback engagement artifact. | |
| REVIEW_CLI_TOKENS_ARTIFACT_PATH: ${{ runner.temp }}/agent-tokens.jsonl | |
| # Datadog telemetry the cli emits during the run (synthesis | |
| # + finding events, run cost / duration). Required for npm- | |
| # installed consumers — the published binary is what | |
| # `bunx --bun @uniswap/review-cli` runs from, and that's the | |
| # SOURCE TypeScript (no `--define` bake). Only the standalone | |
| # GitHub-Releases binary has the build-time bake. Without | |
| # this env passthrough the DD client is a silent no-op. | |
| DD_API_KEY: ${{ secrets.DD_API_KEY }} | |
| DD_SITE: ${{ vars.DATADOG_SITE || 'datadoghq.com' }} | |
| GH_REPO: ${{ github.repository }} | |
| PR_NUMBER: ${{ needs.triage.outputs.pr_number }} | |
| REVIEW_CLI_BIN: ${{ steps.install-review-cli.outputs.bin-path }} | |
| run: | | |
| # Bash-array build for the optional flags. ${VAR:+--flag "$VAR"} | |
| # would word-split after the expansion (the inner quotes are | |
| # literal text in the result, not shell syntax), turning a | |
| # multi-word note into multiple positional args and erroring | |
| # out the cli. | |
| args=() | |
| [ -n "$REVIEW_NOTE" ] && args+=(--note "$REVIEW_NOTE") | |
| [ -n "$REVIEW_COMMENTER" ] && args+=(--commenter "$REVIEW_COMMENTER") | |
| [ -n "$REVIEW_THREAD_ANCHOR" ] && args+=(--thread-anchor "$REVIEW_THREAD_ANCHOR") | |
| "$REVIEW_CLI_BIN/review-cli" review "$PR_NUMBER" \ | |
| --repo "$GH_REPO" \ | |
| --trigger-source "$REVIEW_TRIGGER_SOURCE" \ | |
| "${args[@]}" | |
| # Post — reads `last-run.json` and pushes findings, resolves | |
| # threads, upserts the sticky. Always run after Analyze, even | |
| # on failure, so Post can recover the most-recent saved state. | |
| - name: Post | |
| if: success() || failure() | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| PR_NUMBER: ${{ needs.triage.outputs.pr_number }} | |
| REVIEW_CLI_BIN: ${{ steps.install-review-cli.outputs.bin-path }} | |
| run: '"$REVIEW_CLI_BIN/review-cli" post "$PR_NUMBER" --repo "$GH_REPO"' | |
| - name: Upload run artifact | |
| # Run even when review-cli failed — the artifact still has | |
| # whatever events it captured up to the failure, which is | |
| # the most valuable thing you can read post-hoc. | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: review-cli-run-${{ needs.triage.outputs.pr_number }}-${{ github.run_attempt }} | |
| path: ${{ runner.temp }}/review-cli-output.json | |
| if-no-files-found: warn | |
| retention-days: 30 | |
| # Per-run agent cost record stream — name MUST be exactly | |
| # `agent-tokens` so the agent-scorecard aggregator finds it | |
| # across consumer repos with one `gh run download` call. | |
| - name: Upload agent-tokens artifact | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: agent-tokens | |
| path: ${{ runner.temp }}/agent-tokens.jsonl | |
| if-no-files-found: ignore | |
| retention-days: 30 | |
| overwrite: true | |
| # Optional — stamp the CI Visibility pipeline span with the review | |
| # outcome so platform engineers looking at the GH Actions run in | |
| # Datadog's CI page can filter by verdict / team / depth. This is | |
| # additive: review-cli already ships rich metrics + logs via HTTPS; | |
| # this just makes the CI page itself useful (team filtering on the | |
| # CI page requires a `team:` tag on the pipeline span). | |
| # | |
| # Configure via repo secrets / vars: | |
| # secrets.DD_API_KEY — required; absence skips this step | |
| # vars.DATADOG_SITE — optional, defaults to datadoghq.com | |
| # vars.REVIEW_DD_TEAM — optional, the DD Team slug to tag | |
| # | |
| # `if:` gates on `env.HAS_DATADOG_API_KEY`, computed at job level | |
| # from `secrets.DD_API_KEY`. `secrets.*` isn't a valid named | |
| # value in step-level `if:` (the workflow won't even parse), and | |
| # step-level `env:` isn't applied before `if:` is evaluated — | |
| # job-level env IS, hence the indirection. | |
| - name: Stamp DD CI Visibility with review outcome | |
| if: always() && env.HAS_DATADOG_API_KEY == 'true' | |
| env: | |
| DATADOG_API_KEY: ${{ secrets.DD_API_KEY }} | |
| DATADOG_SITE: ${{ vars.DATADOG_SITE || 'datadoghq.com' }} | |
| REVIEW_TEAM: ${{ vars.REVIEW_DD_TEAM || '' }} | |
| ARTIFACT: ${{ runner.temp }}/review-cli-output.json | |
| run: | | |
| set -uo pipefail | |
| if [ ! -s "$ARTIFACT" ]; then | |
| echo "no review-cli artifact at $ARTIFACT — skipping DD CI tagging" | |
| exit 0 | |
| fi | |
| VERDICT=$(jq -r '.review.verdict // "unknown"' "$ARTIFACT") | |
| DEPTH=$(jq -r '.review.depth // "unknown"' "$ARTIFACT") | |
| FINDINGS=$(jq -r '(.review.findings // []) | length' "$ARTIFACT") | |
| # `review.clean`, not `review.skipped`. The artifact has no | |
| # pipeline-skip signal — review-cli emits `review.run.skipped` | |
| # only as a metric event when the run is actually skipped | |
| # (e.g. trivial-diff guard fired), and that path doesn't write | |
| # an artifact at all. APPROVE ∧ zero findings means the run | |
| # completed cleanly, not that it was skipped. Tagging it as | |
| # `skipped` here conflates the two — particularly here, where | |
| # `trivial_threshold: 0` in .claude/review.yml disables the | |
| # size-skip path entirely, so every artifact is from a real | |
| # review. | |
| CLEAN=$([ "$FINDINGS" = 0 ] && [ "$VERDICT" = "APPROVE" ] && echo true || echo false) | |
| TAGS=( | |
| --tags "service:review-cli" | |
| --tags "review.verdict:$VERDICT" | |
| --tags "review.depth:$DEPTH" | |
| --tags "review.clean:$CLEAN" | |
| ) | |
| if [ -n "$REVIEW_TEAM" ]; then | |
| TAGS+=(--tags "team:$REVIEW_TEAM") | |
| fi | |
| bunx @datadog/datadog-ci@5.15.0 tag --level pipeline "${TAGS[@]}" \ | |
| || echo "datadog-ci tag failed (non-fatal)" | |
| bunx @datadog/datadog-ci@5.15.0 measure --level pipeline \ | |
| --measures "review.findings:$FINDINGS" \ | |
| || echo "datadog-ci measure failed (non-fatal)" | |
| # Swap the 👀 ack for ✅/❌ on comment-triggered runs. | |
| # `--clear-prior-ack` removes the eyes reaction first so the | |
| # comment ends up with one terminal signal. | |
| - name: Update reaction (✅ / ❌) on comment trigger | |
| if: | | |
| always() && | |
| needs.triage.outputs.comment_id != '' | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| OUTCOME: ${{ job.status }} | |
| GH_REPO: ${{ github.repository }} | |
| COMMENT_ID: ${{ needs.triage.outputs.comment_id }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| REVIEW_CLI_BIN: ${{ steps.install-review-cli.outputs.bin-path }} | |
| run: | | |
| REACTION=$([ "$OUTCOME" = "success" ] && echo "success" || echo "failure") | |
| "$REVIEW_CLI_BIN/review-cli" react \ | |
| --repo "$GH_REPO" \ | |
| --comment-id "$COMMENT_ID" \ | |
| --event "$EVENT_NAME" \ | |
| --reaction "$REACTION" \ | |
| --clear-prior-ack | |
| # Edit the running-state reply with the terminal verdict so the | |
| # requester sees responsive feedback without scrolling back up to | |
| # the sticky. Only fires when the triage step posted a reply | |
| # (reply_id non-empty) AND the trigger was a comment. | |
| - name: Update threaded reply with terminal state | |
| if: | | |
| always() && | |
| needs.triage.outputs.reply_id != '' | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| OUTCOME: ${{ job.status }} | |
| GH_REPO: ${{ github.repository }} | |
| REPLY_ID: ${{ needs.triage.outputs.reply_id }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| REVIEW_CLI_BIN: ${{ steps.install-review-cli.outputs.bin-path }} | |
| run: | | |
| if [ "$OUTCOME" = "success" ]; then | |
| BODY="✅ **Reviewed** · [view run ↗](${RUN_URL}) · scroll up for the full summary." | |
| else | |
| BODY="⚠ **Review failed** · [view run ↗](${RUN_URL}) · scroll up for the partial state, or comment \`@request-claude-review\` to retry." | |
| fi | |
| "$REVIEW_CLI_BIN/review-cli" reply \ | |
| --repo "$GH_REPO" \ | |
| --event "$EVENT_NAME" \ | |
| --edit-reply "$REPLY_ID" \ | |
| --body "$BODY" |