Skip to content

refactor(ci): extract reusable CI checks and setup-bun composite action#152

Merged
wyattjoh merged 4 commits intomainfrom
wyattjoh/ci-workflow-refactor
Apr 17, 2026
Merged

refactor(ci): extract reusable CI checks and setup-bun composite action#152
wyattjoh merged 4 commits intomainfrom
wyattjoh/ci-workflow-refactor

Conversation

@wyattjoh
Copy link
Copy Markdown
Contributor

@wyattjoh wyattjoh commented Apr 13, 2026

Summary

  • Add .github/actions/setup-bun composite action (Bun install + optional
    dependency caching + bun install). Used by release.yml's versioning,
    canary-version, and notify-failure jobs, all of which run in trusted
    contexts (push to main or workflow_call from push to main).
  • Add snapshot-ci job in release.yml that reuses ci.yml via
    workflow_call, gating snapshot publishing on CI passing first.
  • Gate all release paths (stable, canary, snapshot) on CI checks passing
    before versioning or building.

ci.yml keeps inline oven-sh/setup-bun@v2 + actions/cache + bun install
in each job rather than adopting the composite action. Switching would trip
CodeQL's "checkout of untrusted code in a privileged context" finding via
the issue_comment -> snapshot-ci -> ci.yml path, since uses: ./...
would resolve action.yml from the untrusted PR ref.

Test plan

  • CI workflow runs correctly on a PR (build, lint, test, e2e)
  • Release workflow gates on CI passing before versioning
  • Snapshot workflow gates on CI passing before building
  • Fork PRs and Dependabot PRs correctly skip e2e tests
  • Failure notifications still fire when jobs fail

@wyattjoh
Copy link
Copy Markdown
Contributor Author

wyattjoh commented Apr 13, 2026

Stack: slack-notifications

Part of a stacked PR chain. Do not merge manually.

@wyattjoh wyattjoh force-pushed the wyattjoh/ci-workflow-refactor branch 2 times, most recently from 57bd831 to 3e2a016 Compare April 13, 2026 21:44
@wyattjoh wyattjoh force-pushed the wyattjoh/slack-notifications branch from 4bc124f to 546e8d5 Compare April 13, 2026 23:23
@wyattjoh wyattjoh force-pushed the wyattjoh/ci-workflow-refactor branch from 3e2a016 to 82653fa Compare April 13, 2026 23:23
@wyattjoh wyattjoh force-pushed the wyattjoh/slack-notifications branch from 546e8d5 to 7d6c0cf Compare April 14, 2026 20:41
@wyattjoh wyattjoh force-pushed the wyattjoh/ci-workflow-refactor branch 2 times, most recently from a52dece to f771daf Compare April 15, 2026 20:06
@wyattjoh wyattjoh force-pushed the wyattjoh/slack-notifications branch from 2503552 to c6b93f8 Compare April 15, 2026 20:06
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 15, 2026

⚠️ No Changeset found

Latest commit: 1236ad1

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 15, 2026

⚠️ No Changeset found

Latest commit: f771daf

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@wyattjoh wyattjoh force-pushed the wyattjoh/slack-notifications branch from c6b93f8 to 0e5c2d0 Compare April 15, 2026 21:00
@wyattjoh wyattjoh force-pushed the wyattjoh/ci-workflow-refactor branch 2 times, most recently from 484281b to d181be8 Compare April 15, 2026 22:41
@wyattjoh wyattjoh force-pushed the wyattjoh/slack-notifications branch 2 times, most recently from f9e59a5 to 21ed234 Compare April 15, 2026 23:41
@wyattjoh wyattjoh force-pushed the wyattjoh/ci-workflow-refactor branch from d181be8 to 402aa66 Compare April 16, 2026 00:41
@wyattjoh wyattjoh force-pushed the wyattjoh/slack-notifications branch from 21ed234 to ba656ce Compare April 16, 2026 20:02
@wyattjoh wyattjoh force-pushed the wyattjoh/ci-workflow-refactor branch 2 times, most recently from 3858f38 to 23b792d Compare April 16, 2026 21:51
Base automatically changed from wyattjoh/slack-notifications to main April 16, 2026 22:26
@wyattjoh wyattjoh force-pushed the wyattjoh/ci-workflow-refactor branch from 23b792d to 50707ee Compare April 16, 2026 22:34
@wyattjoh wyattjoh marked this pull request as ready for review April 16, 2026 22:34
Comment thread .github/actions/setup-bun/action.yml Fixed
Comment thread .github/actions/setup-bun/action.yml Fixed
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 16, 2026

📝 Walkthrough

Walkthrough

Adds a composite GitHub Action at .github/actions/setup-bun/action.yml that installs Bun, optionally restores or saves Bun’s dependency cache, then runs bun install --frozen-lockfile. Makes .github/workflows/ci.yml callable via workflow_call with inputs ref and run-e2e, updates actions/checkout in jobs to use ref: ${{ inputs.ref }}, and changes test-e2e conditional logic to run for same-repo PRs (excluding dependabot) or for non-PR events when inputs.run-e2e is true. Replaces inline Bun setup/install steps in notify-failure.yml and release.yml with the new composite action (using cache: restore in release jobs). Adds a ci job and a snapshot-ci job in release.yml that call the CI workflow and make them prerequisites for versioning and snapshot build/failure-notify steps.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Summary of changes

  • New composite action: .github/actions/setup-bun/action.yml
    • Input: cache ("none", "restore", "read-write", default "none")
    • Steps: installs Bun via oven-sh/setup-bun@v2; conditionally uses actions/cache@v5 or actions/cache/restore@v5 for ~/.bun/install/cache keyed by bun-${{ runner.os }}-${{ hashFiles('bun.lock') }}; runs bun install --frozen-lockfile
  • CI workflow (.github/workflows/ci.yml)
    • Added workflow_call trigger with inputs:
      • ref: string (default "")
      • run-e2e: boolean (default true)
    • Updated jobs to pass ref: ${{ inputs.ref }} to actions/checkout@v6
    • Adjusted test-e2e condition: runs for same-repo PRs excluding dependabot[bot], and for non-PR events only when inputs.run-e2e is true
  • notify-failure workflow (.github/workflows/notify-failure.yml)
    • Replaced direct oven-sh/setup-bun@v2 + bun install steps with uses: ./.github/actions/setup-bun (with cache: restore)
    • Slack notification step unchanged
  • Release workflow (.github/workflows/release.yml)
    • Added job ci that calls ./.github/workflows/ci.yml (runs on push, secrets: inherit)
    • Updated versioning to needs: [ci]
    • Replaced Bun setup in versioning and canary-version with uses: ./.github/actions/setup-bun (with: { cache: restore })
    • Added job snapshot-ci that calls ./.github/workflows/ci.yml with with: { ref: ${{ needs.snapshot.outputs.sha }} } and secrets: inherit
    • Made snapshot-build depend on [snapshot, snapshot-ci]
    • Updated failure-notification jobs to include the new ci/snapshot-ci dependencies where applicable
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main changes: extracting a reusable setup-bun composite action and refactoring CI checks into a reusable workflow pattern.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The pull request description is well-aligned with the changeset, clearly explaining the addition of the setup-bun composite action, workflow_call trigger in ci.yml, and gating of release paths on CI checks.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
.github/workflows/release.yml (1)

368-372: Consider using the setup-bun composite action here for consistency.

The snapshot job still uses the inline oven-sh/setup-bun@v2 and bun install --frozen-lockfile pattern, while versioning and canary-version have been updated to use ./.github/actions/setup-bun. For consistency and maintainability, consider updating this job as well.

♻️ Suggested refactor
       - uses: actions/checkout@v6
         with:
           ref: ${{ steps.pr.outputs.sha }}
-      - uses: oven-sh/setup-bun@v2
-      - run: bun install --frozen-lockfile
+      - uses: ./.github/actions/setup-bun
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 368 - 372, Replace the inline bun
setup in the snapshot job: remove the two steps that use oven-sh/setup-bun@v2
and run "bun install --frozen-lockfile" and instead call the local composite
action ./.github/actions/setup-bun (same pattern used by versioning and
canary-version). Keep the existing checkout step and its ref, and ensure the new
uses: ./.github/actions/setup-bun step provides any required inputs or outputs
that the rest of the job needs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.github/workflows/release.yml:
- Around line 368-372: Replace the inline bun setup in the snapshot job: remove
the two steps that use oven-sh/setup-bun@v2 and run "bun install
--frozen-lockfile" and instead call the local composite action
./.github/actions/setup-bun (same pattern used by versioning and
canary-version). Keep the existing checkout step and its ref, and ensure the new
uses: ./.github/actions/setup-bun step provides any required inputs or outputs
that the rest of the job needs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: aacbe784-6a49-4a9a-a968-48bfcabf49c3

📥 Commits

Reviewing files that changed from the base of the PR and between 395f454 and 50707ee.

📒 Files selected for processing (4)
  • .github/actions/setup-bun/action.yml
  • .github/workflows/ci.yml
  • .github/workflows/notify-failure.yml
  • .github/workflows/release.yml

- Add .github/actions/setup-bun composite action that encapsulates Bun
  installation, optional dependency caching, and bun install
- Add .github/workflows/ci-checks.yml reusable workflow containing the
  full CI suite (build, lint, test, e2e) with configurable ref and
  run-e2e inputs
- Simplify ci.yml to a single ci-checks.yml call
- Gate release.yml stable/canary/snapshot paths on CI checks passing
  before versioning or building
- Update notify-failure.yml to use the setup-bun composite action
Add workflow_call trigger to ci.yml so it serves as both the PR workflow
and a reusable workflow called by release.yml. This eliminates the
separate ci-checks.yml indirection.
@wyattjoh wyattjoh force-pushed the wyattjoh/ci-workflow-refactor branch from 50707ee to 522ce87 Compare April 17, 2026 19:26
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
.github/workflows/release.yml (1)

39-39: Inconsistent cache input usage compared to ci.yml.

The versioning job (line 39) and canary-version job (line 186) use the composite action without specifying cache:, which defaults to "none" (no caching). In contrast, ci.yml always explicitly sets cache to either read-write or restore.

For consistency and to match the caching behavior pattern used in ci.yml, explicitly specify the cache mode:

Add explicit cache specification
       - uses: ./.github/actions/setup-bun
+        with:
+          cache: restore
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml at line 39, The composite action invocation
"uses: ./.github/actions/setup-bun" in the release workflow's versioning and
canary-version jobs omits the cache input; update those invocations to
explicitly include a cache mode (e.g., add "cache: read-write" or "cache:
restore" as used in ci.yml) so the action's caching behavior is consistent with
ci.yml—locate the two occurrences of "uses: ./.github/actions/setup-bun" in the
release.yml (in the versioning and canary-version jobs) and add the chosen
"cache:" input to each.
.github/workflows/ci.yml (1)

60-66: Review the cache poisoning risk through a composite action in the untrusted ref.

The setup-bun composite action is executed from the checked-out untrusted ref, allowing a malicious action.yml to run arbitrary steps. While the specific code path (lines 60-66) uses cache: restore (read-only), the build job earlier in the workflow uses cache: read-write with the same deterministic cache key (bun-${{ runner.os }}-${{ hashFiles('bun.lock') }}). A malicious build job could write poisoned content to the cache, which subsequent jobs would then read.

Current mitigations:

  • pull_request trigger: limited write access
  • workflow_call restrictions: called only on push to main or snapshot builds

However, if the untrusted ref shares the same bun.lock hash as a previous build, the cache key would collide. Consider pinning the composite action checkout to a trusted ref (e.g., main) separately from the code checkout to prevent executing a malicious action definition, though this requires additional workflow restructuring.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 60 - 66, The composite action
./.github/actions/setup-bun is being checked out from the untrusted ref
(actions/checkout@v6) which permits a malicious action.yml to run and poison the
shared cache (cache key bun-${{ runner.os }}-${{ hashFiles('bun.lock') }}); fix
by pinning the composite action to a trusted ref: either (A) replace the
local-relative use with an explicit repository reference pinned to a trusted ref
(e.g., uses: your-org/your-repo/.github/actions/setup-bun@main) or (B) perform a
second, separate actions/checkout@v6 for the .github/actions/setup-bun path with
ref: main (or other trusted branch) before the step that uses ./
.github/actions/setup-bun, so the composite action is always sourced from a
trusted commit while leaving the main checkout for the untrusted ref intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 60-66: The composite action ./.github/actions/setup-bun is being
checked out from the untrusted ref (actions/checkout@v6) which permits a
malicious action.yml to run and poison the shared cache (cache key bun-${{
runner.os }}-${{ hashFiles('bun.lock') }}); fix by pinning the composite action
to a trusted ref: either (A) replace the local-relative use with an explicit
repository reference pinned to a trusted ref (e.g., uses:
your-org/your-repo/.github/actions/setup-bun@main) or (B) perform a second,
separate actions/checkout@v6 for the .github/actions/setup-bun path with ref:
main (or other trusted branch) before the step that uses ./
.github/actions/setup-bun, so the composite action is always sourced from a
trusted commit while leaving the main checkout for the untrusted ref intact.

In @.github/workflows/release.yml:
- Line 39: The composite action invocation "uses: ./.github/actions/setup-bun"
in the release workflow's versioning and canary-version jobs omits the cache
input; update those invocations to explicitly include a cache mode (e.g., add
"cache: read-write" or "cache: restore" as used in ci.yml) so the action's
caching behavior is consistent with ci.yml—locate the two occurrences of "uses:
./.github/actions/setup-bun" in the release.yml (in the versioning and
canary-version jobs) and add the chosen "cache:" input to each.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d70cc922-6e8a-4e58-bd4a-c81466bd1943

📥 Commits

Reviewing files that changed from the base of the PR and between 50707ee and 522ce87.

📒 Files selected for processing (4)
  • .github/actions/setup-bun/action.yml
  • .github/workflows/ci.yml
  • .github/workflows/notify-failure.yml
  • .github/workflows/release.yml
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/notify-failure.yml
  • .github/actions/setup-bun/action.yml

Comment thread .github/workflows/release.yml
@rafa-thayto
Copy link
Copy Markdown
Contributor

rafa-thayto commented Apr 17, 2026

.github/workflows/release.yml, snapshot job (lines 371-372):

versioning and canary-version already moved to the composite — lining up snapshot keeps all three identical and saves the next person from wondering why this one is the odd one out.

      - uses: actions/checkout@v6
        with:
          ref: ${{ steps.pr.outputs.sha }}
-      - uses: oven-sh/setup-bun@v2
-      - run: bun install --frozen-lockfile
+      - uses: ./.github/actions/setup-bun

Add cache-writable input to ci.yml (default true). snapshot-ci passes
false so the build job on an untrusted PR head SHA cannot populate the
main-branch-scoped cache via the issue_comment reusable-workflow
context, closing a cache-poisoning vector surfaced by CodeQL through
the snapshot path.

Also set cache: restore explicitly on the versioning and canary-version
jobs in release.yml, matching the pattern already used in ci.yml so
both consume the cache populated by the push-to-main build.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/ci.yml (1)

25-27: ⚠️ Potential issue | 🟠 Major

Use inputs.ref in concurrency group to prevent cross-caller cancellations.

Lines 25-27 group runs by github.ref, which in a reusable workflow equals the caller's ref. When snapshot-ci in .github/workflows/release.yml calls this workflow with a different SHA via the ref input, the concurrency bucket remains the same (both use the caller's default branch ref), causing unrelated reusable-workflow runs to cancel each other.

Suggested fix
 concurrency:
-  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || inputs.ref || github.ref }}
   cancel-in-progress: true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 25 - 27, The concurrency group
currently uses github.ref which can be the caller's ref for reusable workflows
and causes unrelated runs to cancel; update the concurrency group expression to
prefer the caller-provided ref input (github.event.inputs.ref) before falling
back to github.ref, e.g. change group to use ${{ github.workflow }}-${{
github.event.pull_request.number || github.event.inputs.ref || github.ref }},
referencing the concurrency/group key and the github.event.inputs.ref fallback
to prevent cross-caller cancellations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In @.github/workflows/ci.yml:
- Around line 25-27: The concurrency group currently uses github.ref which can
be the caller's ref for reusable workflows and causes unrelated runs to cancel;
update the concurrency group expression to prefer the caller-provided ref input
(github.event.inputs.ref) before falling back to github.ref, e.g. change group
to use ${{ github.workflow }}-${{ github.event.pull_request.number ||
github.event.inputs.ref || github.ref }}, referencing the concurrency/group key
and the github.event.inputs.ref fallback to prevent cross-caller cancellations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 802fd3be-f5f0-42e8-9222-bfcb8a7e1938

📥 Commits

Reviewing files that changed from the base of the PR and between 522ce87 and e8071a9.

📒 Files selected for processing (2)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml

…nding

Revert ci.yml jobs to inline oven-sh/setup-bun@v2 + actions/cache +
bun install, instead of ./.github/actions/setup-bun. CodeQL flags
uses: ./<local-action> after checkout of an untrusted ref when the
workflow is reachable from an issue_comment trigger (via snapshot-ci),
because a malicious action.yml would execute in the default-branch
cache scope regardless of inputs passed to the composite.

The cache-writable input added in the previous commit did not close
this vector because CodeQL (correctly) does not assume action.yml
honors inputs. Drop the input and the snapshot-ci override.

Composite action remains in use by release.yml's versioning and
canary-version jobs, which run on push to main (trusted context).
@wyattjoh wyattjoh merged commit 6e8256c into main Apr 17, 2026
10 checks passed
@wyattjoh wyattjoh deleted the wyattjoh/ci-workflow-refactor branch April 17, 2026 20:40
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.

3 participants