Files
android/.github/workflows/sdlc-sdk-update-evaluate.yml
renovate[bot] 396cf41117 [deps]: Update actions/checkout action to v7 (#7332)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-09-01 14:07:21 +00:00

190 lines
9.0 KiB
YAML

name: SDLC / SDK Update Evaluation
on:
pull_request:
types: [opened, synchronize]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions: {}
jobs:
evaluate:
name: Evaluate SDK Update
runs-on: ubuntu-24.04
if: ${{ github.event.pull_request.head.ref == 'sdlc/sdk-update' }}
permissions:
id-token: write
actions: read
contents: write
packages: read
pull-requests: write
steps:
- name: Skip if this bump was already evaluated
id: gate
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
run: |
LATEST_SUBJECT=$(gh pr view "$PR_NUMBER" -R "$REPO" --json commits --jq '.commits[-1].messageHeadline')
if [[ "$LATEST_SUBJECT" != "SDK Update - "* ]]; then
echo "Branch tip is not a fresh SDK version bump ('$LATEST_SUBJECT') — already evaluated, skipping."
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi
- name: Log in to Azure
if: steps.gate.outputs.skip == 'false'
uses: bitwarden/gh-actions/azure-login@main
with:
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
client_id: ${{ secrets.AZURE_CLIENT_ID }}
- name: Get Azure Key Vault secrets
id: get-kv-secrets
if: steps.gate.outputs.skip == 'false'
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: gh-org-bitwarden
secrets: "ANTHROPIC-RESPONSE-API-KEY,BW-GHAPP-ID,BW-GHAPP-KEY"
- name: Log out from Azure
if: steps.gate.outputs.skip == 'false'
uses: bitwarden/gh-actions/azure-logout@main
- name: Generate GH App token
if: steps.gate.outputs.skip == 'false'
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: app-token
with:
client-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }}
private-key: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-KEY }}
permission-contents: write
permission-pull-requests: write
- name: Check out repo
if: steps.gate.outputs.skip == 'false'
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
token: ${{ steps.app-token.outputs.token }}
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
persist-credentials: true
- name: Clone sdk-internal
if: steps.gate.outputs.skip == 'false'
run: git clone --quiet https://github.com/bitwarden/sdk-internal "${{ github.workspace }}/../sdk-internal"
- name: Setup Android Build
if: steps.gate.outputs.skip == 'false'
uses: ./.github/actions/setup-android-build
- name: Pre-fetch the SDK artifact
if: steps.gate.outputs.skip == 'false'
# claude-code-action replaces GITHUB_TOKEN in the environment it hands Claude's Bash
# tool, so Gradle cannot authenticate to GitHub Packages from there. Resolving the
# dependency here populates ~/.gradle/caches, letting Claude's own compile read the
# fixed SDK version straight from cache. continue-on-error keeps a genuine compile
# break reaching Claude, which is the evaluation this workflow exists to perform.
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew app:compileStandardDebugKotlin authenticator:compileDebugKotlin
- name: Verify the SDK artifact reached the Gradle cache
if: steps.gate.outputs.skip == 'false'
# A cold cache reaches Claude as a dependency-resolution error it could mistake for a
# break the SDK bump caused, so fail here where the cause is unambiguous. Metadata
# alone does not let the compile run, hence the check for the .aar rather than the dir.
run: |
version=$(sed -n 's/^bitwardenSdk = "\([^"]*\)"/\1/p' gradle/libs.versions.toml)
if [ -z "$version" ]; then
echo "::error::Could not read bitwardenSdk from gradle/libs.versions.toml."
exit 1
fi
cache_root="${GRADLE_USER_HOME:-$HOME/.gradle}/caches/modules-2/files-2.1"
artifact=$(find "$cache_root/com.bitwarden/sdk-android/$version" \
-name '*.aar' -print -quit 2>/dev/null || true)
if [ -z "$artifact" ]; then
echo "::error::sdk-android $version is absent from the Gradle cache; the pre-fetch did not resolve it."
exit 1
fi
echo "Cached sdk-android $version at $artifact"
- name: Run Claude Code
id: claude
if: steps.gate.outputs.skip == 'false'
uses: anthropics/claude-code-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1.0.183
with:
anthropic_api_key: ${{ steps.get-kv-secrets.outputs.ANTHROPIC-RESPONSE-API-KEY }}
allowed_bots: "bw-ghapp"
bot_id: "178206702"
bot_name: "bw-ghapp[bot]"
track_progress: true
use_sticky_comment: true
plugin_marketplaces: "https://github.com/bitwarden/ai-plugins.git"
plugins: |
bitwarden-delivery-tools@bitwarden-marketplace
bitwarden-software-engineer@bitwarden-marketplace
prompt: |
PR #${{ github.event.pull_request.number }} in ${{ github.repository }} bumps the
Bitwarden SDK. Evaluate it yourself using the `evaluating-sdk-internal-updates`
skill's Identify steps (1-7). `bitwarden/sdk-internal` is already cloned as a sibling
directory at `${{ github.workspace }}/../sdk-internal`, satisfying step 1's
prerequisite — use it directly.
If step 7 turns up anything to resolve, dispatch a single subagent on the
`bitwarden-software-engineer` agent to invoke `Skill(evaluating-sdk-internal-updates)`
itself and carry out its Resolve steps (8-10) for every finding in one pass — decide
the fix, implement it, and commit on the current branch (already checked out) using
`Skill(bitwarden-delivery-tools:committing-changes)` for the message. Hand the
subagent all the findings (commit, symbol, call sites for each) so it doesn't redo the
investigation, dispatching it once for every finding rather than once per finding. Do
not push; the workflow pushes separately. Have it report back the commit SHA and each
finding's disposition. Skip the subagent entirely if step 7 finds nothing to resolve.
Once the subagent has reported back — or immediately, if there was nothing to resolve —
update the sticky PR comment yourself with the step 7 report,
structured as: "## SDK bump evaluated" (old → new version, commit count), "## Compile-time
breaks" (found-and-fixed, or "none found"), "## Runtime considerations" (any behavioral
notes worth flagging, or omit the section if there are none), "## Everything else in
range — confirmed safe" (brief, one line per commit or group), and "## Commit" (the SHA,
or "no commit — nothing needed fixing"). Always post this, even when the conclusion is
that nothing needs fixing — the comment is the audit trail that the check ran.
claude_args: |
--model opus
--allowedTools "Agent,Bash(gh pr diff:*),Bash(git -C * log *),Bash(git -C * show *),Bash(git add:*),Bash(git commit:*),Bash(git log:*),Bash(git show:*),Bash(git diff:*),Bash(grep:*),Bash(./gradlew *),Read,Grep,Glob,Edit,Write,Skill,mcp__github_comment__update_claude_comment"
- name: Push any resolved fix
if: steps.gate.outputs.skip == 'false'
env:
_BRANCH_NAME: ${{ github.event.pull_request.head.ref }}
run: |
# claude-code-action treats the PR head as untrusted and restores these exact paths from
# origin/main before running Claude, regardless of whether this branch's copy is stale.
# This job never has a legitimate reason to change them, so a diff here is that restore,
# not Claude — exclude them rather than flag it.
_TRUSTED_CONFIG_PATHSPEC=(
':!.claude' ':!.mcp.json' ':!.claude.json' ':!.gitmodules' ':!.ripgreprc'
':!CLAUDE.md' ':!CLAUDE.local.md' ':!.husky'
)
if [ -n "$(git status --porcelain -- . "${_TRUSTED_CONFIG_PATHSPEC[@]}")" ]; then
echo "::error::Claude left uncommitted changes; nothing was pushed."
git status --porcelain -- . "${_TRUSTED_CONFIG_PATHSPEC[@]}"
exit 1
fi
if git diff --quiet "origin/$_BRANCH_NAME" HEAD; then
echo "No new commits to push."
else
git push origin "HEAD:$_BRANCH_NAME"
fi