Skip to content

fix(ci): restore npm, Swift, and Zepp releases without a bypass credential - #2052

Merged
Asherlc merged 3 commits into
mainfrom
Asherlc/astana
Jul 27, 2026
Merged

Asherlc merged 3 commits into
mainfrom
Asherlc/astana

Conversation

@Asherlc

@Asherlc Asherlc commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Root cause

The Infisical sync is authoritative over GitHub Actions secrets. ZEPP_RELEASE_TOKEN existed only as a manually created repo secret, so the sync pruned it at 18:38Z — between the last successful release (18:04Z) and the first failure (19:12Z). One token named for Zepp gated three unrelated release pipelines.

Secondary finding

Release npm Packages has never completed a release — no chore(release): publish commit exists. lerna.json pushed a [skip ci] version commit straight to main, and both branch rulesets have an empty bypass_actors list, so no credential — including a repo-admin token — could ever have satisfied the required checks. Restoring the deleted token would not have fixed npm.

Changes

Workflow Fix Credential
Zepp GITHUB_TOKEN + job-scoped contents: write none
Swift App token scoped to the mirror repo App
npm Publish-only; OIDC + tags after none
npm (new) version-npm.yml: auto-merging version PR App

Auto-merge waits for the 9 required checks rather than bypassing them.

Two latent bugs fixed along the way:

  • [skip ci] would have stopped checks running on the version PR, hanging auto-merge forever.
  • version-npm and release-npm fire on the same CI run; without a guard the former would re-bump before the latter tagged, looping forever. Guarded by skipping while any version is untagged.

Validation

actionlint clean on all four workflows; every run block passes bash -n; tagging loop exercised against the 15 real packages (0 duplicates). App key verified to sign a valid JWT for dofek-release with exactly contents: write + pull_requests: write.

Before merging

RELEASE_APP_CLIENT_ID (var) and RELEASE_APP_PRIVATE_KEY (Infisical → synced) are both in place. Consider narrowing the App installation from all repos to just dofek + whoop-ble-swift.

🤖 Generated with Claude Code


Summary by cubic

Restores npm, Swift, and Zepp releases by replacing the pruned ZEPP_RELEASE_TOKEN with scoped, least‑privilege auth. Zepp uses GITHUB_TOKEN, Swift mints a short‑lived GitHub App token scoped to the mirror owner/repo, and npm splits versioning/publishing with tags pushed post‑publish.

  • Bug Fixes

    • Zepp: use GITHUB_TOKEN with job‑scoped contents: write; remove hard secret check.
    • Swift: mint token via actions/create-github-app-token scoped to the mirror owner/repo; use for checkout and release; drop token_secret from .github/swift-packages.json.
    • npm: add version-npm.yml to open and auto‑merge a version PR using an App token (workflows run); release-npm.yml only publishes with OIDC (id-token: write) and tags only newly created <name>@<version> tags; guard prevents re‑bumping while any version is untagged; update lerna message to chore(release): version npm packages.
  • Migration

    • Ensure RELEASE_APP_CLIENT_ID (repo var) and RELEASE_APP_PRIVATE_KEY (secret via Infisical) are set.
    • Install the release GitHub App on the Swift mirror owner or the specific mirror repo.
    • No npm token is required; publishing relies on trusted publishing.
    • Optional: restrict the App installation to dofek and whoop-ble-swift.

Written for commit 4002a77. Summary will update on new commits.

Review in cubic

Asherlc and others added 2 commits July 26, 2026 18:34
…ntial

The Infisical sync is authoritative over GitHub Actions secrets and pruned
ZEPP_RELEASE_TOKEN, which had only ever been set directly on the repo. All
three release workflows depended on it.

Zepp only ever needed contents: write on this repo, so it now uses
GITHUB_TOKEN. The Swift mirror genuinely needs cross-repo access and mints a
short-lived GitHub App token scoped to that repository alone.

npm could never have worked as written: lerna pushed a "[skip ci]" version
commit straight to main, and both branch rulesets have empty bypass_actors,
so no credential could have satisfied the required checks. Versioning now
goes through an auto-merging pull request that actually runs those checks,
and publishing is OIDC-only with release tags pushed afterwards. A guard
skips versioning while a release is still untagged so the two workflows
cannot bump on top of each other.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 27, 2026 01:36
@cursor

cursor Bot commented Jul 27, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@codereviewbot-ai

codereviewbot-ai Bot commented Jul 27, 2026

Copy link
Copy Markdown

🤖 Review complete!


🤖 Reviewed by codereviewbot.ai - Catch bugs before your team does.

@sourcery-ai sourcery-ai Bot 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.

Sorry @Asherlc, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@Asherlc, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 8 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4a43fb6a-9308-4ddc-a2ac-70473a7b016f

📥 Commits

Reviewing files that changed from the base of the PR and between 4b657e5 and 4002a77.

📒 Files selected for processing (8)
  • .github/swift-packages.json
  • .github/workflows/release-npm.yml
  • .github/workflows/release-swift-packages.yml
  • .github/workflows/release-zepp.yml
  • .github/workflows/version-npm.yml
  • docs/package-publishing.md
  • docs/production-incident-baseline.md
  • lerna.json

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Restore Zepp, Swift, and npm releases by removing shared secret dependency

🐞 Bug fix ✨ Enhancement 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Remove dependency on pruned ZEPP_RELEASE_TOKEN across release workflows.
• Rework npm release to publish via OIDC and version via auto-merged PR.
• Mint scoped GitHub App tokens for cross-repo SwiftPM mirror updates.
Diagram

graph TD
  A["CI workflow run"] --> B["version-npm.yml"] --> C["Version PR (auto-merge)"] --> D["main"]
  A --> E["release-npm.yml"] --> F{{"npm registry (OIDC)"}}
  A --> G["release-swift-packages.yml"] --> H{{"SwiftPM mirror repo"}}
  A --> I["release-zepp.yml"] --> J{{"GitHub Release"}}
  K{{"GitHub App token"}} --> B --> C
  K --> G --> H
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Restore a single shared PAT secret via Infisical
  • ➕ Fastest operational rollback with minimal workflow logic changes
  • ➕ Keeps existing release flows largely intact
  • ➖ Reintroduces a single point of failure across unrelated pipelines
  • ➖ Harder to least-privilege: PAT scopes tend to be broad
  • ➖ Still leaves npm's branch-protection/required-checks dead-end unless versioning strategy changes
2. Use GitHub App token for all write operations (including tags/releases)
  • ➕ Centralizes privileged operations under an auditable, scoped identity
  • ➕ Can restrict to specific repos and specific permissions
  • ➖ More setup overhead (app provisioning, installation scoping, key rotation)
  • ➖ Unnecessary for same-repo releases where GITHUB_TOKEN + job permissions is sufficient
3. Single unified release pipeline (reusable workflow) with explicit ordering
  • ➕ Reduces duplicated 'Confirm tested commit is current' logic
  • ➕ Easier to ensure version→publish→tag ordering without concurrency edge cases
  • ➖ Bigger refactor and higher blast radius
  • ➖ May couple independent release concerns and slow iteration

Recommendation: Keep the PR’s approach: it removes the fragile shared secret, applies least-privilege per workflow (GITHUB_TOKEN for Zepp, repo-scoped App token for Swift, OIDC for npm), and fixes the npm pipeline’s fundamental inability to satisfy required checks by moving versioning to a PR that actually runs them. The added guard against version/publish races is a pragmatic fix without a full pipeline refactor.

Files changed (7) +229 / -59

Bug fix (1) +3 / -5
release-zepp.ymlUse GITHUB_TOKEN with contents:write for Zepp GitHub releases +3/-5

Use GITHUB_TOKEN with contents:write for Zepp GitHub releases

• Adds job-scoped contents: write permission and replaces ZEPP_RELEASE_TOKEN with GITHUB_TOKEN. Removes the explicit 'missing secret' guard since the workflow no longer depends on an external token secret.

.github/workflows/release-zepp.yml

Documentation (1) +54 / -0
production-incident-baseline.mdDocument the release token sync incident and remediation +54/-0

Document the release token sync incident and remediation

• Adds an incident entry describing the Infisical secret sync pruning ZEPP_RELEASE_TOKEN, the resulting release failures, and why npm was structurally unable to pass required checks. Records the implemented mitigation strategy and follow-ups (app installation scoping, potential auto-merge stalling conditions).

docs/production-incident-baseline.md

Other (5) +172 / -54
swift-packages.jsonRemove token_secret requirement from Swift package metadata +0/-1

Remove token_secret requirement from Swift package metadata

• Drops the per-package token secret field from the Swift package registry config, aligning with the move away from ZEPP_RELEASE_TOKEN. This simplifies the matrix inputs for the Swift release workflow.

.github/swift-packages.json

release-npm.ymlConvert npm release workflow to publish-only + post-publish tagging +35/-49

Convert npm release workflow to publish-only + post-publish tagging

• Removes version-bump behavior and the dependency on ZEPP_RELEASE_TOKEN, switching to OIDC trusted publishing (id-token: write) and job-scoped contents: write for pushing tags. Adds logic to tag all published package versions after a successful publish.

.github/workflows/release-npm.yml

release-swift-packages.ymlMint repo-scoped GitHub App token for SwiftPM mirror updates +19/-3

Mint repo-scoped GitHub App token for SwiftPM mirror updates

• Removes token_secret validation/usage and replaces it with actions/create-github-app-token to mint a short-lived installation token scoped to the mirror repo. Uses that token for checkout and GH CLI operations against the SwiftPM distribution repository.

.github/workflows/release-swift-packages.yml

version-npm.ymlAdd versioning workflow that opens and auto-merges a version PR +117/-0

Add versioning workflow that opens and auto-merges a version PR

• Introduces a new workflow_run-driven pipeline that mints a GitHub App token, opens a version bump PR, and enables auto-merge so required checks run normally. Includes a guard that skips versioning while any package version is untagged to prevent version/publish feedback loops.

.github/workflows/version-npm.yml

lerna.jsonUpdate lerna version commit message to avoid [skip ci] +1/-1

Update lerna version commit message to avoid [skip ci]

• Changes the lerna version commit message from a '[skip ci]' publish message to a versioning-focused message. Prevents CI suppression on version PRs, which would otherwise block required checks and auto-merge.

lerna.json

@Asherlc
Asherlc enabled auto-merge (squash) July 27, 2026 01:37
@qodo-code-review

qodo-code-review Bot commented Jul 27, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 149 rules

Grey Divider


Action required

1. Swift token owner mismatch ✓ Resolved 🐞 Bug ≡ Correctness
Description
release-swift-packages.yml mints the GitHub App installation token using `owner: ${{
github.repository_owner }}` even though the target mirror repository owner is taken from
matrix.repository. If the mirror repo is under a different owner/org than the canonical repo,
token minting and the subsequent checkout/push will fail because the token is requested for the
wrong owner’s installation.
Code

.github/workflows/release-swift-packages.yml[R120-136]

+      - name: Resolve SwiftPM distribution repository name
+        if: steps.current.outputs.should_release == 'true'
+        id: mirror
+        env:
+          REPOSITORY: ${{ matrix.repository }}
+        run: echo "name=${REPOSITORY#*/}" >> "$GITHUB_OUTPUT"
+
+      - name: Mint SwiftPM distribution token
+        if: steps.current.outputs.should_release == 'true'
+        id: distribution_token
+        uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
+        with:
+          app-id: ${{ vars.RELEASE_APP_CLIENT_ID }}
+          private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
+          owner: ${{ github.repository_owner }}
+          repositories: ${{ steps.mirror.outputs.name }}
+
Relevance

⭐⭐⭐ High

Owner/repo mismatch can break token minting/checkout; correctness fix likely accepted.

PR-#871

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The workflow allows matrix.repository to be any <owner>/<repo> string, but token minting is
constrained to the canonical repo owner; these two sources can diverge and would break token
creation/checkout for the mirror repo.

.github/workflows/release-swift-packages.yml[54-66]
.github/workflows/release-swift-packages.yml[120-145]
.github/swift-packages.json[4-10]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`actions/create-github-app-token` is called with `owner: ${{ github.repository_owner }}`, but the repo to access is `matrix.repository` (an `<owner>/<repo>` string). If those owners differ, the action will attempt to mint an installation token under the wrong owner and the workflow will fail when minting the token and/or when checking out/pushing to the mirror.

### Issue Context
The workflow explicitly validates that `.repository` is an `<owner>/<repo>` string and then uses it as the checkout target, so the owner should also come from that same value.

### Fix Focus Areas
- .github/workflows/release-swift-packages.yml[54-66]
- .github/workflows/release-swift-packages.yml[120-145]

### Suggested fix
1. Parse both the owner and repo name from `matrix.repository`:
  - `owner=${REPOSITORY%%/*}`
  - `name=${REPOSITORY#*/}`
2. Pass both into `actions/create-github-app-token`:
  - `owner: ${{ steps.mirror.outputs.owner }}`
  - `repositories: ${{ steps.mirror.outputs.name }}`
3. (Optional) Extend the config validation or add a runtime assertion that `matrix.repository` is well-formed and non-empty after parsing.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Uncited Infisical sync claim ✓ Resolved 📘 Rule violation § Compliance
Description
docs/production-incident-baseline.md asserts specific Infisical sync behavior (authoritative sync
that prunes unknown keys) without an adjacent primary-source citation. This violates the
documentation requirement to cite third-party behavior claims with official sources.
Code

docs/production-incident-baseline.md[R18619-18624]

+- **Evidence:** The last successful Zepp release ran at `18:04:36Z` and the
+  first failure at `19:12:48Z`. All 78 GitHub Actions secrets were rewritten
+  between `18:38:07Z` and `18:38:20Z` by the Infisical sync. Comparing the
+  Infisical `prod` key list against the repository secret list left an empty
+  "in GitHub but not in Infisical" set, showing the sync is authoritative and
+  removes keys it does not own.
Relevance

⭐⭐⭐ High

Strong precedent: docs third-party/behavior claims must include adjacent primary-source citations.

PR-#2044
PR-#2046
PR-#2047

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1505719 requires that added/modified sentences claiming third-party platform
behavior include an adjacent link to an official/primary source. The added incident entry claims the
Infisical sync is authoritative and removes keys it does not own, but there is no nearby Infisical
documentation link in that paragraph.

Rule 1505719: Cite third-party behavior claims in docs with primary sources
docs/production-incident-baseline.md[18619-18624]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A third-party behavior claim about Infisical secret sync being authoritative and pruning keys is stated without an adjacent citation to an official/primary source.

## Issue Context
This change is in `docs/production-incident-baseline.md` (under `docs/`), which is in-scope for the rule requiring primary-source citations for third-party platform behavior claims.

## Fix Focus Areas
- docs/production-incident-baseline.md[18619-18624]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Publishing docs now stale ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
docs/package-publishing.md still documents the old npm flow where release-npm.yml runs `lerna
version` and pushes the release commit/tags directly, and it still describes Swift config as
containing a token-secret field. After this PR’s new version-npm.yml workflow and removal of
token_secret from swift-packages.json, the documentation is misleading and will cause
operational errors.
Code

.github/workflows/version-npm.yml[R1-41]

+name: Version npm Packages
+
+on:
+  workflow_run:
+    workflows: ["CI"]
+    branches: [main]
+    types:
+      - completed
+  workflow_dispatch:
+
+permissions:
+  contents: read
+
+concurrency:
+  group: version-npm
+  cancel-in-progress: false
+
+jobs:
+  version:
+    name: Open version pull request
+    if: >
+      github.event_name == 'workflow_dispatch' ||
+      github.event.workflow_run.conclusion == 'success'
+    runs-on: ubuntu-24.04
+    steps:
+      # The App token (not GITHUB_TOKEN) is required here: pull requests opened
+      # with GITHUB_TOKEN do not trigger workflows, so the required checks would
+      # never report and auto-merge would wait forever.
+      - name: Mint release token
+        id: release_token
+        uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
+        with:
+          app-id: ${{ vars.RELEASE_APP_CLIENT_ID }}
+          private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
+
+      - name: Check out canonical source
+        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+        with:
+          fetch-depth: 0
+          ref: main
+          token: ${{ steps.release_token.outputs.token }}
Relevance

⭐⭐⭐ High

They commonly accept doc accuracy/maintenance updates; misleading release docs likely fixed.

PR-#1181
PR-#1394

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The doc’s described npm and Swift release mechanics don’t match the post-PR workflows/config: npm
versioning is now a separate workflow/PR, and Swift config no longer includes token_secret.

docs/package-publishing.md[21-45]
docs/package-publishing.md[70-81]
.github/workflows/version-npm.yml[1-41]
.github/workflows/release-npm.yml[74-102]
.github/swift-packages.json[1-11]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`docs/package-publishing.md` no longer matches the repo’s release implementation:
- npm versioning is now done via `version-npm.yml` PRs, while `release-npm.yml` only publishes + tags.
- Swift package config no longer has `token_secret`, but docs still describe it.

### Issue Context
Keeping release docs aligned with workflows is important because these are operational procedures.

### Fix Focus Areas
- docs/package-publishing.md[21-45]
- docs/package-publishing.md[70-81]
- .github/workflows/version-npm.yml[1-41]
- .github/workflows/release-npm.yml[74-102]

### Suggested fix
Update the npm section to describe:
- `version-npm.yml` opening an auto-merge PR for version bumps
- `release-npm.yml` publishing via OIDC and tagging after publish
Update the Swift section to remove `token_secret` language and describe the App-token approach used by `release-swift-packages.yml`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

4. Unscoped tag push ✓ Resolved 🐞 Bug ☼ Reliability
Description
release-npm.yml pushes tags with git push origin --tags, which will push every local tag that is
missing on the remote, not just the tags created by this step. This is a robustness risk: future
steps (or local tag creation side-effects) could cause unintended tags to be published.
Code

.github/workflows/release-npm.yml[R83-101]

+      - name: Tag published packages
+        if: steps.current.outputs.should_release == 'true'
+        run: |
+          git config user.name "github-actions[bot]"
+          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+          CREATED=0
+          while read -r TAG; do
+            [ -n "$TAG" ] || continue
+            if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then
+              continue
+            fi
+            git tag "$TAG"
+            CREATED=$((CREATED + 1))
+          done < <(pnpm exec lerna list --json --loglevel silent | jq -r '.[] | "\(.name)@\(.version)"')
+          if [ "$CREATED" -eq 0 ]; then
+            echo "All package versions are already tagged."
+          else
+            git push origin --tags
+            echo "Pushed $CREATED release tag(s)."
Relevance

⭐⭐⭐ High

Team accepts workflow robustness around tags; related tag-handling hardening accepted previously.

PR-#1669

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new tagging step explicitly uses git push origin --tags, which is broader than necessary and
not limited to the tags created in the step.

.github/workflows/release-npm.yml[83-102]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The workflow creates package tags and then runs `git push origin --tags`. That push is broader than required and could push unrelated local tags if they exist.

### Issue Context
Today the checkout is clean and this job appears to create only the intended package tags; the concern is future-proofing and preventing accidental publication of any other tags.

### Fix Focus Areas
- .github/workflows/release-npm.yml[83-102]

### Suggested fix
Capture the tag names as they’re created and push only those:
- Append created tag names to an array/list (or write to a temp file).
- After the loop, if the list is non-empty, run:
 - `git push origin refs/tags/<tag1> refs/tags/<tag2> ...`
This keeps the push narrowly scoped to what the step just created.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread docs/production-incident-baseline.md Outdated
Comment thread .github/workflows/release-swift-packages.yml Outdated
Comment thread .github/workflows/release-npm.yml Outdated
Comment thread .github/workflows/version-npm.yml
Scope Swift App tokens to the mirror repo owner, push only created npm tags,
cite Infisical sync behavior, and align package-publishing docs with the new
version/publish split.

Co-authored-by: Cursor <cursoragent@cursor.com>
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants