Skip to content

llm: Add evaluating-sdk-internal-updates skill and CI workflow - #7251

Merged
SaintPatrck merged 17 commits into
mainfrom
chore/add-evaluating-sdk-internal-updates-skill
Aug 11, 2026
Merged

llm: Add evaluating-sdk-internal-updates skill and CI workflow#7251
SaintPatrck merged 17 commits into
mainfrom
chore/add-evaluating-sdk-internal-updates-skill

Conversation

@SaintPatrck

@SaintPatrck SaintPatrck commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

🎟️ Tracking

AI-74

📔 Objective

Adds a new skill, evaluating-sdk-internal-updates, that evaluates a bitwarden/android SDK-bump PR against the sdk-internal commit range for compile-time and runtime breaking changes, maps affected symbols to Android call sites, and resolves in-scope fixes.

Adds .github/workflows/sdlc-sdk-update-evaluate.yml, which runs this skill via Claude Code against the bot's sdlc/sdk-update PR branch on open/synchronize, auto-commits and pushes any resolved fix, and reports results as a sticky PR comment.

@github-actions github-actions Bot added the t:llm Change Type - LLM related change (e.g. CLAUDE.md files) label Aug 7, 2026
@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.31%. Comparing base (3c7dc95) to head (fa15c9e).
⚠️ Report is 15 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7251      +/-   ##
==========================================
+ Coverage   85.88%   86.31%   +0.42%     
==========================================
  Files         934      921      -13     
  Lines       66626    66443     -183     
  Branches     9748     9794      +46     
==========================================
+ Hits        57221    57347     +126     
+ Misses       5925     5592     -333     
- Partials     3480     3504      +24     
Flag Coverage Δ
app-data 17.69% <ø> (+0.13%) ⬆️
app-ui-auth-tools 18.61% <ø> (-0.13%) ⬇️
app-ui-platform 16.57% <ø> (-0.42%) ⬇️
app-ui-vault 27.87% <ø> (+<0.01%) ⬆️
authenticator 6.09% <ø> (-0.03%) ⬇️
lib-core-network-bridge 4.12% <ø> (-0.01%) ⬇️
lib-data-ui 1.20% <ø> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The skill no longer offers to clone sdk-internal or falls back to gh
api calls when it's missing — it locates the local clone and stops
with a clear message if there isn't one, matching how branching and
pushing are already the caller's concern. The CI workflow clones it
as a sibling before invoking Claude, satisfying the prerequisite
directly instead of leaving the decision to an unattended agent.
Step 11 invokes Skill(bitwarden-delivery-tools:committing-changes),
but plugin skills aren't available in claude-code-action by default —
they need an explicit plugin_marketplaces/plugins declaration, same as
respond.yml does for its own plugin set.
Kotlin's TODO() throws NotImplementedError when called. Listing it
alongside genuinely inert options (no-op, null/default return)
contradicted the point of stubbing: satisfying the compiler without
introducing a behavioral decision. A crash is a behavioral decision.
Whether and how to commit a fix depends on the caller (interactive
session vs. CI), same as branching, pushing, and sdk-internal cloning
already do. The CI workflow's own prompt now carries the commit
instruction directly, since it already owned the adjacent push step.
Adds a required pr-number input and a Resolve PR context step that
uses it on manual runs, falling back to the triggering pull_request
event's PR otherwise. Checkout, the push step, and the agent prompt
all read from this resolved context instead of assuming a
pull_request event.
github.event.pull_request.head.ref (a branch name, attacker-influenceable
in general) was spliced directly into the run: script via template
expansion, which happens before the shell parses it. Moved every
event/input-derived value into env and referenced them as shell
variables instead, per GitHub's documented mitigation.
@SaintPatrck
SaintPatrck marked this pull request as ready for review August 11, 2026 15:25
@SaintPatrck
SaintPatrck requested review from a team as code owners August 11, 2026 15:25
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

🤖 Bitwarden Claude Code Review

Overall Assessment: REQUEST CHANGES

Re-reviewed after the four newest commits. The prior findings are addressed: the uncommitted-work guard now uses git status --porcelain (catching staged and untracked work), and step 2's version parsing matches scripts/update-sdk-version.sh's cut -d'-' -f3- including branch-named .dev refs. The new skip gate is sound — sdlc-sdk-update.yml does title its bump commits SDK Update - …, and gh pr view --json commits returns the branch tip last, so an auto-fix push correctly skips the second run. The remaining findings are both on the fix path: the mandated /plan-android-work writes an un-ignored plan document that trips the new guard, and the two commands it routes through are written as interactive, gated workflows.

Code Review Details
  • ⚠️ : /plan-android-work saves to .claude/outputs/plans/, which nothing gitignores, so the guard fails the job and the fix is never pushed
    • .github/workflows/sdlc-sdk-update-evaluate.yml:130
  • ❓ : /work-on-android and /plan-android-work require user confirmation between phases; unattended, the commit phase may never be reached
    • .claude/skills/evaluating-sdk-internal-updates/SKILL.md:30

Comment thread .github/workflows/sdlc-sdk-update-evaluate.yml
that nothing needs fixing — the comment is the audit trail that the check ran.
claude_args: |
--model opus
--allowedTools "Bash(gh pr diff:*),Bash(git -C *:*),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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ IMPORTANT: The prompt asks Claude to commit, but committing isn't permitted — and the push step drops the work silently.

Details and fix

The allowlist enumerates read-only git commands (git -C, git log, git show, git diff); git add and git commit are absent, as is git config. The checkout is also detached at head-sha with no committer identity configured, so git commit would fail with "Author identity unknown" even if allowed.

The failure is silent: line 128 compares origin/$_BRANCH_NAME against HEAD, i.e. committed trees only. Uncommitted edits produce no difference, the step prints "No new commits to push", and the job ends green with the fix discarded along with the runner.

Suggested:

  • add Bash(git add:*),Bash(git commit:*),Bash(git config:*) to --allowedTools
  • seed git config user.name/user.email with the bot identity (as sdlc-sdk-update.yml does) in a step before Claude runs
  • fail loudly on leftover changes before the push check:
if ! git diff --quiet; then
  echo "::error::Claude left uncommitted changes; nothing was pushed."
  exit 1
fi

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipped adding git add/commit/config to allowedTools. Checked the action source, git add/commit are already granted implicitly by tag mode (track_progress forces it). A git config step wouldn't stick either, configureGitAuth authors commits from bot_name/bot_id, not local git config, so set those to bw-ghapp[bot]'s identity instead. Added the fail-loudly check on leftover changes as suggested.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 1501047

Comment on lines +43 to +46
if [ "$EVENT_NAME" == "workflow_dispatch" ]; then
NUMBER="$PR_NUMBER_INPUT"
HEAD_SHA=$(gh pr view "$NUMBER" -R "$REPO" --json headRefOid --jq .headRefOid)
HEAD_REF=$(gh pr view "$NUMBER" -R "$REPO" --json headRefName --jq .headRefName)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ IMPORTANT: The workflow_dispatch path accepts any PR number with no head-repo or branch check.

Details and fix

The pull_request path is gated to sdlc/sdk-update (line 23), but dispatch takes whatever gh pr view returns. Dispatched against a fork PR, this job checks out untrusted code with persist-credentials: true and a GitHub App token holding contents: write, then runs ./gradlew — Gradle build scripts are arbitrary code, which can read that token out of .git/config. The final step would then git push origin "HEAD:<fork branch name>", creating a branch in this repo.

Validate after resolution:

HEAD_REPO=$(gh pr view "$NUMBER" -R "$REPO" --json headRepositoryOwner,headRepository \
  --jq '"\(.headRepositoryOwner.login)/\(.headRepository.name)"')
if [ "$HEAD_REPO" != "$REPO" ] || [ "$HEAD_REF" != "sdlc/sdk-update" ]; then
  echo "::error::Only same-repo sdlc/sdk-update PRs can be evaluated."
  exit 1
fi

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped workflow_dispatch instead of adding this check. detectMode throws for any event outside PR/issue, and use_sticky_comment is gated on isPullRequestEvent, so the manual trigger path doesn't work with claude-code-action regardless of validation. Will add branch/repo checks if manual triggering comes back with a supported mechanism.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in afa443a

Comment thread .github/workflows/sdlc-sdk-update-evaluate.yml
Comment thread .claude/skills/evaluating-sdk-internal-updates/SKILL.md Outdated
Comment thread .claude/skills/evaluating-sdk-internal-updates/SKILL.md Outdated
…ector

claude-code-action's detectMode throws for any event outside the PR/
issue set, and use_sticky_comment is separately gated on isPullRequestEvent.
The manual-trigger path added in b131141/6c94a28d8 can't work with this
action version regardless of how its own validation is scoped. Reverting
to pull_request-only until there's a supported way to drive this manually.
checkHumanActor rejects any non-User actor unless explicitly allow-
listed via allowed_bots, which defaults to empty. The sdlc/sdk-update
PR is opened and pushed by bw-ghapp[bot], so every opened/synchronize
event died at the action's own prepare step before this addition —
neither the prompt nor the skill ever ran.
settings.gradle.kts authenticates maven.pkg.github.com/bitwarden/sdk
with GITHUB_TOKEN, and this job had neither that token, packages: read,
nor the JDK 21 / Gradle setup sdlc-sdk-update.yml's own Test job uses.
Without this, every ./gradlew invocation fails at dependency
resolution — steps 3 and 10 of the skill become misleading rather
than merely unavailable, since they report a compile-time break
directly from the failure.
configureGitAuth authors commits from the action's bot_name/bot_id
defaults (claude[bot]), not our intended identity. sdlc-sdk-update.yml
hard-fails when the branch tip's author isn't 178206702+bw-ghapp[bot],
so the first fix commit would have permanently blocked the SDK update
pipeline on that branch. Setting bot_id/bot_name to match.

Separately, the push step compared committed trees only (origin vs.
HEAD), so an edit Claude started but never committed produced no
diff, printed "No new commits to push," and the job went green having
silently done nothing. Now fails the job instead.
Bash(./gradlew*compileKotlin*:*) requires the literal substring
compileKotlin; step 3's compileStandardDebugKotlin ends in
DebugKotlin, so the rule never matched and blocked both the
compile-break pre-check and the fix verification. Library modules
also use compileDebugKotlin (no product flavors), so a narrower
per-flavor pattern would still need to cover both spellings — using
a plain ./gradlew wildcard instead. Also fixed step 10's stale
"compileKotlin" restatement to reference step 3 instead of drifting
from it again.
"Kotlin symbols surface as com.bitwarden.sdk.*" was wrong — UniFFI
emits one package per crate (com.bitwarden.core, .vault, .crypto,
etc.); only the top-level Client/AuthClient/GeneratorClients actually
live under com.bitwarden.sdk. Grepping for that import prefix in a
fixed app/core/network/ui module list also skipped authenticator
entirely, which depends on the SDK too (implementation(libs.bitwarden.sdk)
in authenticator/build.gradle.kts) and has its own real call sites.
An authenticator-only break would have been reported as "no call
sites" and step 3's single-module compile wouldn't have caught it
either. Searching the whole repo for the bare symbol name avoids both
gaps.
App-token pushes fire synchronize (unlike GITHUB_TOKEN), so every
auto-fix commit re-triggers this workflow for a second full run
against code that's already fixed. Checks the branch tip's commit
subject via the API before any Azure/build setup — sdlc-sdk-update.yml
always titles its bump commits "SDK Update - ...", so anything else
on the tip (our own prior fix, or a manual change) means this bump was
already evaluated and the rest of the job is skipped.
@SaintPatrck SaintPatrck added the ai-review Request a Claude code review label Aug 11, 2026
Comment thread .github/workflows/sdlc-sdk-update-evaluate.yml Outdated
Comment thread .claude/skills/evaluating-sdk-internal-updates/SKILL.md Outdated
Comment thread .github/workflows/sdlc-sdk-update-evaluate.yml Outdated
20 minutes was a guess and didn't obviously cover the fix path (cold
Gradle setup + compile + sdk-internal crawl + plan/work-on-android's
own implement/test/verify/preflight/commit cycle + re-compile), and a
timeout cancels the job before the push step ever runs, discarding any
commit that only ever existed on the runner. Falling back to the
platform default rather than guessing a number.
Bare git diff only compares the working tree to the index — a file
that's git add'ed but not committed shows no diff (worktree matches
index), and untracked files are invisible to git diff entirely. Both
cases fell through to the committed-trees comparison, printed "No new
commits to push", and discarded the fix. A new required SDK method is
exactly the case likely to need a new file. git status --porcelain
covers staged, unstaged, and untracked in one check.
… SHA

"Trailing segment is the SHA" breaks for .dev SDK builds
(scripts/update-sdk-version.sh's own usage examples show both
1.0.0-2586-20e3dfa6 and 1.0.0-2577-fix-wasm-import), which land on
sdlc/sdk-update routinely since that's the Update mode's default
package. sdlc-sdk-update.yml extracts the same field with
cut -d'-' -f3- specifically because it "handles both commit hashes
and branch names." Taking a literal branch name as OLD/NEW in step 4
would fail to resolve in the clone; step 2 now says to resolve it as
origin/<branch> instead.
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Claude Code Configuration Validation — PR #7251

Scope: .claude/skills/evaluating-sdk-internal-updates/SKILL.md (new skill, 31 lines / 601 body words)

Also read as required context: .github/workflows/sdlc-sdk-update-evaluate.yml — the second file in this PR and the only thing that invokes the skill. Several findings below are contract mismatches that are only visible across the two files, so the workflow is cited where relevant even though it is not itself a .claude/ file.

Validation Status
1. Plugin structure (plugin-validator) Skipped — no plugin directories changed; this is a repo-level .claude/ skill
2. Skill review (skill-reviewer) Run — findings below
3. Config & security review (reviewing-claude-config) Run — security scan clean; structural findings below

Environment note: the checkout arrived with SKILL.md deleted in the working tree (unstaged) while present at HEAD. Local HEAD (fa15c9ee3) matches the PR head SHA exactly, so this was a harness artifact, not a PR change. The file was restored from HEAD and reviewed as committed.


✅ Security scan — clean

All four mandatory security checks pass. No blocking security issues.

  • No committed settings.local.jsongit ls-files returns nothing.
  • No hardcoded secrets — scanned the new skill for OpenAI/GitHub token prefixes, and generic apiKey|password|token|secret assignment patterns. Clean.
  • Workflow secret handling is correct — every credential is an expression, never a literal: Azure OIDC login (AZURE_*), Key Vault fetch for ANTHROPIC-RESPONSE-API-KEY/BW-GHAPP-ID/BW-GHAPP-KEY, explicit azure-logout at line 57, GH App token minted with narrowed permission-contents/permission-pull-requests (lines 68-69).
  • No dangerous command auto-approvals — no rm -rf, chmod 777, piped curl|sh, dd, mkfs, or push --force in either file.
  • Workflow permissions are default-deny — top-level permissions: {} (line 11) with narrowed job-level grants (lines 18-23). Third-party actions are SHA-pinned. The job is gated to the bot branch (head.ref == 'sdlc/sdk-update', line 17), and the pull_request trigger means fork PRs get no secrets.

✅ Frontmatter & structure — valid

Parsed with a real YAML loader: name present and kebab-case, matching the directory. description present, 321 chars, third person, with concrete triggers ("Update SDK to" PR, bitwardenSdk version change, sdk-internal breaking changes). No tabs. 31 lines — far under the 500-line guideline. Referenced commands plan-android-work and work-on-android both exist in .claude/commands/. Single-file layout is correct at this size; references/ would be premature.


Findings

🔴 Critical — errors, must fix

These are functional defects that break the automation. All three were independently verified against the repo, not just reported.

C1. A successful fix run will fail the job — the plan artifact dirties the tree

SKILL.md:30 · .claude/commands/plan-android-work.md:105-108 · sdlc-sdk-update-evaluate.yml:130-134

Step 9 mandates /plan-android-work. That command's Phase 4 saves a design document to .claude/outputs/plans/<NAME>-PLAN.md. Verified: git check-ignore reports that path is not ignored, and .gitignore has no claude or outputs entry. The push step aborts on any dirty tree:

if [ -n "$(git status --porcelain)" ]; then
  echo "::error::Claude left uncommitted changes; nothing was pushed."; exit 1

So the plan file alone fails the job and discards the fix it was written to produce. This fires on the success path, not an edge case.

Fix (pick one): add .claude/outputs/ to .gitignore; or instruct step 9 to skip Phase 4 (do not save a plan file); or drop the /plan-android-work hop and plan in-context.

C2. Steps delegate to interactive commands that commit and open a PR

SKILL.md:27, 30 · work-on-android.md:44, 59, 64

Verified in work-on-android.md: line 64 — "Never proceed to another phase without user confirmation"; line 44 — Phase 5 commits; line 59 — Phase 7 creates a pull request. There is no user in this job. Worse, committing and PR-creation directly contradict the workflow prompt's "do not push; the workflow pushes separately" (lines 111-112) and duplicate its own commit instruction. Phase 3 also runs the full test/lint/detekt suite — now unbounded since the job-level timeout was removed (b02782ba1).

Fix: in step 9, name the applicable phases and the stop point — e.g. "Phases 1-3 of Skill(work-on-android) only; do not commit, review, or open a PR — the caller commits" — and state that no user is present so confirmation gates are skipped.

C3. Committing is instructed but not permitted

sdlc-sdk-update-evaluate.yml:123 vs :110-112

--allowedTools grants Bash(gh pr diff:*),Bash(git -C *:*),Bash(git log:*),Bash(git show:*),Bash(git diff:*)no git add, git commit, or git status. The prompt requires committing via Skill(bitwarden-delivery-tools:committing-changes). The only rule that could cover it is Bash(git -C *:*), and only if the model happens to phrase it git -C . commit. When it doesn't, the run ends with uncommitted changes → C1's failure path.

Fix: add Bash(git add:*),Bash(git commit:*),Bash(git status:*) to line 123.


🟠 Major — warnings, should fix

M1. The step-4 search misses the most common class of UniFFI break

SKILL.md:20

-G'uniffi::export|derive\(uniffi|#\[uniffi' only flags commits whose added/removed lines contain a uniffi token. Adding a field to an already-annotated #[derive(uniffi::Record)] struct, or a variant to an already-annotated enum, produces hunks with no uniffi token — the commit never becomes a candidate. Those are exactly the changes that break Kotlin named-argument constructors and exhaustive when. This silently defeats the guarantee on line 9. (The regex itself is valid ERE; the escaping is correct.)

Fix: two-phase search — enumerate the full range (git log --oneline OLD..NEW -- '*.rs'), collect annotated type names at OLD (git grep -n 'uniffi::' OLD -- '*.rs'), then search the range diff for those type names, not just the annotations.

M2. Wrong Gradle task for authenticator; test sources never compiled

SKILL.md:19, 31

Verified: authenticator/build.gradle.kts declares no flavorDimensions/productFlavors (only buildTypes at line 88), unlike app (line 141). So authenticator:compileStandardDebugKotlin does not exist — the correct task is authenticator:compileDebugKotlin. Separately, compileStandardDebugKotlin compiles no unit-test sources, and hundreds of test files import com.bitwarden.{vault,core,sdk,crypto}. A record/enum change that breaks fixtures passes step 3, passes step 10, gets committed and pushed, and fails CI in the test job.

Fix: ./gradlew app:compileStandardDebugUnitTestKotlin authenticator:compileDebugUnitTestKotlin, or delegate task naming to Skill(build-test-verify). Note .claude/skills/build-test-verify/SKILL.md:61 carries the same wrong flavor assumption for authenticator — worth correcting in the same PR so it stops propagating.

M3. Bash(git -C *:*) is over-scoped — permission-scoping issue

SKILL.md:4 and sdlc-sdk-update-evaluate.yml:123

The pattern is well-formed, but prefix-matching on git -C <anything> also matches git -C . push, git -C . reset --hard, git -C . checkout, and git -C . config. In the workflow this combines with persist-credentials: true (line 78) and a GH App token with contents: write — so the agent can push despite the prompt saying "do not push", bypassing the safety check at line 130. Per the priority framework, "permissions broader than needed" is an IMPORTANT-class issue.

Fix: narrow to the read-only subcommands actually used — Bash(git -C * log:*), Bash(git -C * show:*), Bash(git -C * rev-parse:*) — in both files.

M4. Prohibitions are expressed through the tool list, which can't enforce them

SKILL.md:4 vs sdlc-sdk-update-evaluate.yml:123

The skill's allowed-tools omits Edit/Write, which reads as enforcing step 9's "never edit the fix in yourself". The workflow grants both at line 123, so the omission enforces nothing. Keep prohibitions as explicit body rules (step 9 already does this correctly) and don't rely on the tool list to express them.

Also worth verifying against current Claude Code docs before relying on it: skill allowed-tools is understood to be an additive per-turn pre-approval, not a restriction — every tool remains callable. If that holds, the omission is purely cosmetic. Flagged rather than asserted, as this was not verified locally.

M5. Skill(plan-android-work) may not cover argument-passing invocations

SKILL.md:4

Skill(name) is exact-match; covering an invocation with arguments is understood to require Skill(name *). Step 9 explicitly passes arguments ("pass it the step 7 findings", "pass it the resulting plan"). Confirm against current docs, and if it holds, change both entries to Skill(plan-android-work *) / Skill(work-on-android *).

M6. The report contract lives in the workflow, not the skill

SKILL.md:23 vs sdlc-sdk-update-evaluate.yml:114-120

Step 7 says only "Report: compile-time breaks, runtime breaks, safe/no-call-site". The actual required structure is in the workflow prompt. Interactive invocation therefore gets no format, and the two will drift. The workflow also demands an "Everything else in range — confirmed safe, one line per commit or group" section that no skill step can produce — step 4 yields candidates only, and nothing enumerates the full range.

Fix: move the report template into the skill; add a full-range enumeration step (which M1's fix also requires).

M7. Step 1 is unreachable in the only environment that runs it

SKILL.md:17

"stop and tell the user" has no user in CI; the workflow patches around it in the prompt (lines 108-110), making step 1 dead text. There is also no permitted tool for locating the clone — Glob won't traverse above the workspace and there is no Bash(ls:*).

Fix: make the path explicit (check $SDK_INTERNAL_PATH, then ../sdk-internal, validate with git -C <path> rev-parse --git-dir) and define the non-interactive failure mode: report the missing prerequisite, change nothing.

M8. No failure path for an unresolvable ref — fails silently as "safe"

SKILL.md:18, 20

If NEW isn't reachable in the fresh clone (force-push, fork build, deleted .dev branch), git log OLD..NEW errors and steps 5-7 degrade into "nothing found" — which then reads as "confirmed safe" in the PR comment. A silent false-negative on a breaking-change detector is the worst failure mode available.

Fix: add a fallback (git -C <path> fetch origin <sha>) and an explicit abort-with-report outcome.


🟡 Minor — suggestions

  • SKILL.md:2-4 — no version: field. Verified: 5 of 7 sibling skills have one (build-test-verify, implementing-android-code, planning-android-implementation, refining-android-requirements, testing-android-code). Add for consistency.
  • SKILL.md (whole file) — no <thinking> blocks. The skills checklist calls for structured-thinking guidance at decision points; step 5's "classify per hunk" is the natural place.
  • Missing break-classification criteria — step 5 says "classify per hunk" with no criteria, and lines 15-16 assert the subtle cases with no worked example. A short table mapping change → compile-time vs runtime (field added/removed/reordered, enum variant added, method added to a with_foreign trait, error variant added, Option<T> → required) would carry real weight. This, plus the M6 template, is the content that would move the body from 601 words toward the 1,000-word guidance — content, not padding.
  • SKILL.md:13 — "Both app and authenticator depend on the SDK" understates the surface; SDK packages are imported across roughly 8 modules including core, network, data, ui, cxf, authenticatorbridge. Reword to "8 modules reference SDK types; never restrict the search to app".
  • SKILL.md:22 — "grep the whole repo for the bare symbol name" with no noise control. Generic names (Cipher, Kdf, Attachment) flood results, and build/ contains generated bindings. Recommend Grep with glob: "**/*.kt", excluding build/.
  • SKILL.md:27 — restates steps 8-9 and mildly contradicts step 8 (frames every finding as resolved-by-fix; step 8 says report instead of guessing when unsure). Cut to a one-line section pointer.
  • SKILL.md:29 — one ~110-word step carrying six distinct rules. Split into bullets. "Within scope" is asserted three times and never defined — add a boundary, e.g. "in scope = restoring compilation and equivalent behavior; out of scope = adopting new SDK capabilities, refactors, product decisions".
  • SKILL.md:27, 30 vs :4 — notation drift: body uses /plan-android-work, frontmatter and sibling commands use Skill(...). Standardize.
  • SKILL.md:3 — description collides with reviewing-changes, which claims "any review task … dependency updates" and triggers on "review PR". An SDK bump PR matches both. Add a disambiguation clause.
  • SKILL.md:4Bash(./gradlew*:*): the pre-:* wildcard is redundant; Bash(./gradlew:*) is clearer. Functionally harmless.
  • sdlc-sdk-update-evaluate.yml:92GITHUB_TOKEN is exported into the Claude Code step's environment for Gradle's GitHub Packages resolution. Reasonable and scoped, but it does place a live token in the environment of a step with broad Bash access. Worth a comment noting the tradeoff was considered.

Verdict

Request changes. Security posture is genuinely good — nothing in this PR leaks a credential, over-grants a filesystem permission, or auto-approves a destructive command, and the workflow's secret handling and default-deny permissions are better than typical.

The problems are in the automation contract, and they matter because of how they fail. C1 and C3 fail closed on the success path: the run does the analysis, produces the fix, then throws it away. M1, M2, and M8 fail silently — the job posts "confirmed safe" to the PR when it never actually looked. For a check whose entire purpose is catching breaking changes before they land, a silent false negative is worse than a crash.

The analytical content is the strong part and should survive the fixes: lines 13-16 encode facts that are wrong-by-default (annotations scattered outside crates/bitwarden-uniffi, bitwarden-ffi as a decoy, macro invocations hiding their expansion, with_foreign meaning additive is never safe), and "never TODO() — it throws at runtime" is specific and correct for this codebase.

Suggested order: C1 + C3 first (one-line fixes that unblock the happy path) → C2 (scope the delegation) → M1/M2/M8 (close the silent-failure holes) → M3 (narrow the git permission) → the rest.

Comment thread .github/workflows/sdlc-sdk-update-evaluate.yml
Comment thread .claude/skills/evaluating-sdk-internal-updates/SKILL.md

@aj-rosado aj-rosado left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@SaintPatrck
SaintPatrck added this pull request to the merge queue Aug 11, 2026
Merged via the queue into main with commit 8842524 Aug 11, 2026
108 checks passed
@SaintPatrck
SaintPatrck deleted the chore/add-evaluating-sdk-internal-updates-skill branch August 11, 2026 21:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review Request a Claude code review t:llm Change Type - LLM related change (e.g. CLAUDE.md files)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants