Skip to content

ci: migrate workflows to Blacksmith - #1249

Merged
ryoppippi merged 20 commits into
mainfrom
codex/migrate-blacksmith-ci
Jun 10, 2026
Merged

ryoppippi merged 20 commits into
mainfrom
codex/migrate-blacksmith-ci

Conversation

@ryoppippi

@ryoppippi ryoppippi commented Jun 10, 2026 •

Copy link
Copy Markdown
Member

Migrates the GitHub Actions fleet to Blacksmith runners, using larger Blacksmith Linux, macOS ARM, and Windows runners for CI and release preparation.

What changed:

  • Replaces nix-community/cache-nix-action with a pinned useblacksmith/stickydisk mount for Linux /nix stores.
  • Skips macOS Intel and Windows ARM native package builds on pull requests while keeping them in the release matrix.
  • Splits release publishing so Blacksmith packs npm tarballs and the final GitHub-hosted job only runs pnpm publish with provenance for OIDC.
  • Replaces the old Actions cache GC with weekly Nix store GC for Blacksmith sticky disks older than 7 days.

Validation:

  • direnv exec . actionlint
  • direnv exec . just fmt
  • direnv exec . just flake-check

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed macOS binary compatibility with system library paths.
  • Chores

    • Updated CI/CD infrastructure for improved build reliability and performance across platforms.
    • Refactored workflow jobs and cache management strategies.

Notes:

Move CI, release preparation, scheduled automation, and lightweight gate jobs onto Blacksmith runners. Keep macOS Intel and Windows ARM only in the release native package matrix because those runner families are not available on Blacksmith.

Replace nix-community/cache-nix-action with a pinned useblacksmith/stickydisk mount for the Linux /nix store, using stable package/dev keys per runner architecture so Nix builds reuse persistent store state on every Blacksmith Linux job.

Replace the old GitHub Actions cache cleanup workflow with weekly Blacksmith sticky disk Nix garbage collection for package and dev profiles, deleting store paths older than seven days.

Split npm release publishing so Blacksmith restores native artifacts and packs npm tarballs, while the final GitHub-hosted job only runs pnpm publish with provenance for OIDC trusted publishing.
@coderabbitai

coderabbitai Bot commented Jun 10, 2026 •

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR migrates CI and release workflows to Blacksmith self-hosted runners, introduces platform-specific Nix caching composites (Linux sticky-disk and macOS Nix cache), extracts native package builds into per-platform composites, replaces GitHub Actions cache GC with Nix store GC, adds actionlint config for runner validation, and fixes Darwin binary distribution by rewriting libiconv dylib references to system paths.

Changes

Nix Infrastructure and Action Setup

Layer / File(s) Summary
Actionlint configuration
.github/actionlint.yaml
New config restricting self-hosted runner labels to blacksmith-* and suppressing unknown permission scope findings for workflow files.
Refactor setup-nix to use platform-specific actions
.github/actions/setup-nix/action.yaml
Remove cache-profile input, add Linux sticky-disk step before Nix install, replace inline macOS cache with delegated composite action.
Linux Blacksmith sticky-disk composite action
.github/actions/setup-linux-blacksmith-sticky-disk/action.yaml
New action enabling Blacksmith sticky-disk for /nix and ./node_modules mounts based on environment token; resolves pnpm version; manages mount permissions and ownership.
macOS Nix cache composite action
.github/actions/setup-macos-nix-cache/action.yaml
New action wrapping nix-community/[email protected] with OS/arch-keyed caching, hash-based restore keys, and tuned GC settings.
Native build cache composite action
.github/actions/setup-native-build-cache/action.yaml
New action providing conditional cargo registry/git/target caching for Windows builds keyed by Rust lock/toolchain hashes and run identifiers.

Per-Platform Native Package Composites

Layer / File(s) Summary
Linux native package composite action
.github/actions/build-linux-native-package/action.yaml
Build static binary via Nix, verify with file/ldd, stage via script, archive and upload with error-on-missing.
macOS Cargo native package composite action
.github/actions/build-macos-cargo-native-package/action.yaml
Build ccusage via Cargo release, stage for darwin, archive and upload tarball.
macOS Nix native package composite action
.github/actions/build-macos-nix-native-package/action.yaml
Build ccusage derivation via nix build, stage for darwin, archive and upload tarball.
Windows native package composite action
.github/actions/build-windows-native-package/action.yaml
Set up Windows cache, build via Cargo, stage for win32 platform, archive and upload.

Workflow Runner Migrations and Composite Integration

Layer / File(s) Summary
Cache GC workflow replacement
.github/workflows/cache-gc.yaml
Rename to "Sticky Disk GC", add nix-store-gc job matrix for Linux arm64/x64 running nix-collect-garbage --delete-older-than 7d.
CI workflow runner and setup-nix updates
.github/workflows/ci.yaml
Migrate all jobs to blacksmith-* runner labels, remove cache-profile: dev inputs, update build matrix, expand npm publish to explicit package list, add minimum-release-age=0 to E2E runs.
Release workflow runner updates and composite integration
.github/workflows/release.yaml
Update build-native-packages matrix to Blacksmith runners, set persist-credentials: false, replace inlined build steps with per-platform composite action calls, update release job runner.
Update-pricing workflow runner migration
.github/workflows/update-pricing.yaml
Change both pricing jobs to blacksmith-32vcpu-ubuntu-2404-arm, remove cache-profile: dev from setup-nix.

Minor Updates and Fixes

Layer / File(s) Summary
PR gate trigger update
.github/workflows/pr-gate.yaml
Change event trigger from pull_request_target to pull_request.
Pullfrog runner migration
.github/workflows/pullfrog.yml
Update Pullfrog job runner to blacksmith-32vcpu-ubuntu-2404-arm.
Darwin postInstall libiconv fix
package.nix
Add Darwin-only postInstall hook rewriting installed ccusage binary's libiconv dylib dependency from /nix/store path to system /usr/lib/libiconv.2.dylib, fixing distribution on non-Nix Macs.

🎯 3 (Moderate) | ⏱️ ~25 minutes


🐰 A rabbit's tale of Blacksmith and cache,
Sticky disks spinning, with nary a scratch,
Darwin's fixed libiconv, macOS now grins,
Composites packed—where the native build wins!
Self-hosted runners, fast as a hare, 🏃‍♂️

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'ci: migrate workflows to Blacksmith' accurately summarizes the main change: migrating CI workflows to use Blacksmith runners instead of GitHub-hosted runners.
Linked Issues check ✅ Passed The PR addresses issue #1251 (darwin binary libiconv linking issue) by adding a Darwin-specific postInstall hook in package.nix that replaces the bundled libiconv dylib path with the system /usr/lib/libiconv.2.dylib, directly fixing the hardcoded /nix/store path problem.
Out of Scope Changes check ✅ Passed All changes directly support the PR objectives: Blacksmith runner migrations, sticky disk setup for Linux/macOS, native package building via new composite actions, and the libiconv fix in package.nix for issue #1251. No unrelated changes detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/migrate-blacksmith-ci

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

@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 10, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
ccusage-guide f8a302d Commit Preview URL

Branch Preview URL
Jun 10 2026, 08:03 PM

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
.github/actions/setup-nix/action.yaml (1)

16-18: 💤 Low value

Consider more restrictive permissions for the Nix store mount point.

Using chmod -R 777 on /nix is overly permissive. While this is a transient state before Nix takes ownership, a more restrictive permission like 755 would suffice for Nix installation to proceed and reduces the window for unintended writes.

♻️ Suggested fix
-        sudo chmod -R 777 /nix
+        sudo chmod -R 755 /nix
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/actions/setup-nix/action.yaml around lines 16 - 18, Change the
overly-permissive chmod in the action run block: instead of `chmod -R 777 /nix`
use a more restrictive mode (e.g., `chmod 755 /nix`) so the created `/nix/store`
still allows installation but prevents world-writable access; keep the `sudo
mkdir -p /nix/store` and apply the permission change non-recursively to `/nix`
as shown in the run script snippet.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/cache-gc.yaml:
- Line 33: The checkout step currently uses
actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 and leaves Git
credentials persisted; update the checkout invocation to include
persist-credentials: false so credentials are not exposed to later steps (i.e.,
add the persist-credentials: false option to the actions/checkout step in the
workflow).

In @.github/workflows/release.yaml:
- Around line 133-135: The checkout step using
actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 currently leaves Git
credentials persisted; update the checkout action configuration (the uses:
actions/checkout step) to add persist-credentials: false under the with block so
credentials are not saved for downstream steps that only download/publish
artifacts.
- Around line 136-139: The workflow uses actions/setup-node (the step currently
referencing actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e) and
should explicitly disable the package manager cache to prevent cache poisoning;
update that setup-node step to include package-manager-cache: false in the step
inputs (alongside registry-url and node-version) so caching is disabled for
package managers when running the release workflow.
- Around line 145-150: The "Publish npm packages" step runs pnpm publish but
doesn't provide the required NODE_AUTH_TOKEN, causing auth failures; update that
step (the one named "Publish npm packages" that reads packages from
"$RUNNER_TEMP/npm-packages" and runs pnpm publish) to set the environment
variable NODE_AUTH_TOKEN from a secret (e.g. NODE_AUTH_TOKEN: ${{
secrets.NPM_TOKEN }}), or otherwise export the token to npm
(//registry.npmjs.org/:_authToken) before calling pnpm publish so the publish
commands can authenticate.

---

Nitpick comments:
In @.github/actions/setup-nix/action.yaml:
- Around line 16-18: Change the overly-permissive chmod in the action run block:
instead of `chmod -R 777 /nix` use a more restrictive mode (e.g., `chmod 755
/nix`) so the created `/nix/store` still allows installation but prevents
world-writable access; keep the `sudo mkdir -p /nix/store` and apply the
permission change non-recursively to `/nix` as shown in the run script snippet.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f0adc4a9-f9e9-4411-8720-107d5a778341

📥 Commits

Reviewing files that changed from the base of the PR and between da74716 and 863e885.

📒 Files selected for processing (11)
  • .github/actionlint.yaml
  • .github/actions/setup-nix/action.yaml
  • .github/workflows/approve-contributor.yaml
  • .github/workflows/cache-gc.yaml
  • .github/workflows/check-pr-title.yaml
  • .github/workflows/ci.yaml
  • .github/workflows/issue-gate.yaml
  • .github/workflows/pr-gate.yaml
  • .github/workflows/pullfrog.yml
  • .github/workflows/release.yaml
  • .github/workflows/update-pricing.yaml

Comment thread .github/workflows/cache-gc.yaml
Comment thread .github/workflows/release.yaml Outdated
Comment thread .github/workflows/release.yaml Outdated
Comment thread .github/workflows/release.yaml Outdated
Address CodeRabbit feedback by avoiding persisted checkout credentials in release/cache maintenance jobs, disabling setup-node package-manager caching in the OIDC publish job, and tightening the temporary /nix mount point permission before stickydisk mounts.
@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cubic-dev-ai cubic-dev-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.

6 issues found across 11 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".github/workflows/release.yaml">

<violation number="1" location=".github/workflows/release.yaml:152">
P1: `pnpm publish` is executed with a pnpm version pinned to 11.1.1, which is affected by known OIDC trusted-publishing auth issues with setup-node-generated npm auth config; release publishes can fail.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread .github/workflows/pr-gate.yaml Outdated
Comment thread .github/workflows/ci.yaml Outdated
Comment thread .github/workflows/release.yaml Outdated
shell: bash
run: |
while IFS= read -r package; do
pnpm publish "$package" --provenance --no-git-checks --access public

@cubic-dev-ai cubic-dev-ai Bot Jun 10, 2026 •

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.

P1: pnpm publish is executed with a pnpm version pinned to 11.1.1, which is affected by known OIDC trusted-publishing auth issues with setup-node-generated npm auth config; release publishes can fail.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/release.yaml, line 152:

<comment>`pnpm publish` is executed with a pnpm version pinned to 11.1.1, which is affected by known OIDC trusted-publishing auth issues with setup-node-generated npm auth config; release publishes can fail.</comment>

<file context>
@@ -114,12 +111,51 @@ jobs:
+        shell: bash
+        run: |
+          while IFS= read -r package; do
+            pnpm publish "$package" --provenance --no-git-checks --access public
+          done < <(find "$RUNNER_TEMP/npm-packages" -name '*.tgz' -type f | sort)
 
</file context>
Suggested change
pnpm publish "$package" --provenance --no-git-checks --access public
NODE_AUTH_TOKEN="" pnpm publish "$package" --provenance --no-git-checks --access public
Fix with cubic

Comment thread .github/actions/setup-nix/action.yaml Outdated
Comment thread .github/actionlint.yaml Outdated
Comment thread .github/workflows/cache-gc.yaml
Remove the cache-profile input from the setup-nix action so each OS and architecture uses one shared /nix sticky disk. The Nix store is content-addressed and safe to share within the platform scope, so keeping separate package and dev profiles only fragments the cache and doubles GC work.

Simplify the weekly sticky disk GC matrix to match the shared Linux platform keys. Restore the npm publish job shape to the existing GitHub-hosted OIDC publishing flow; only the native package build jobs need the Blacksmith runner migration.
@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Switch the PR gate away from pull_request_target so the contributor gate no longer runs as a privileged target workflow. It stays on the lightweight Blacksmith runner because the job does not need a larger machine.

Enumerate the Blacksmith labels in actionlint so typos are caught during workflow linting. Limit the PR preview package publish to the native packages actually built on PRs; release still builds and publishes the full platform set.

Keep npm release publishing on the existing GitHub-hosted OIDC path. For sticky disk Nix setup, fix mounted /nix ownership after the disk is attached so the single-user Nix installer can write to the store without using a world-writable mount point.
@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release.yaml:
- Around line 99-101: In the npm job's checkout step (the actions/checkout@...
invocation that currently sets fetch-depth: 0), add persist-credentials: false
under the with: block so GitHub Actions will not persist repo credentials to the
workspace; update the with block alongside fetch-depth to include
persist-credentials: false to reduce credential exposure when only downloading
artifacts and publishing packages.
- Around line 103-106: The setup-node step currently configures registry-url and
node-version but leaves package manager caching enabled; update the
actions/setup-node step (the one containing registry-url and node-version:
lts/*) to explicitly disable package manager caching by adding
package-manager-cache: false under its with block so the action will not create
a package manager cache in this privileged workflow (which also uses id-token:
write).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c2924d26-7c06-4a51-b3ca-609a52f0cd73

📥 Commits

Reviewing files that changed from the base of the PR and between a8d686b and 4979344.

📒 Files selected for processing (5)
  • .github/actions/setup-nix/action.yaml
  • .github/workflows/cache-gc.yaml
  • .github/workflows/ci.yaml
  • .github/workflows/release.yaml
  • .github/workflows/update-pricing.yaml
💤 Files with no reviewable changes (2)
  • .github/workflows/update-pricing.yaml
  • .github/workflows/ci.yaml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/release.yaml:
- Around line 99-101: In the npm job's checkout step (the actions/checkout@...
invocation that currently sets fetch-depth: 0), add persist-credentials: false
under the with: block so GitHub Actions will not persist repo credentials to the
workspace; update the with block alongside fetch-depth to include
persist-credentials: false to reduce credential exposure when only downloading
artifacts and publishing packages.
- Around line 103-106: The setup-node step currently configures registry-url and
node-version but leaves package manager caching enabled; update the
actions/setup-node step (the one containing registry-url and node-version:
lts/*) to explicitly disable package manager caching by adding
package-manager-cache: false under its with block so the action will not create
a package manager cache in this privileged workflow (which also uses id-token:
write).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c2924d26-7c06-4a51-b3ca-609a52f0cd73

📥 Commits

Reviewing files that changed from the base of the PR and between a8d686b and 4979344.

📒 Files selected for processing (5)
  • .github/actions/setup-nix/action.yaml
  • .github/workflows/cache-gc.yaml
  • .github/workflows/ci.yaml
  • .github/workflows/release.yaml
  • .github/workflows/update-pricing.yaml
💤 Files with no reviewable changes (2)
  • .github/workflows/update-pricing.yaml
  • .github/workflows/ci.yaml
🛑 Comments failed to post (2)
.github/workflows/release.yaml (2)

99-101: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add persist-credentials: false to the npm job checkout.

The checkout action at line 99 persists Git credentials by default. Since this job only downloads artifacts and publishes packages without needing Git operations, disabling credential persistence reduces the attack surface, especially given the id-token: write permission.

🛡️ Proposed fix
       - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
         with:
           fetch-depth: 0
+          persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          fetch-depth: 0
          persist-credentials: false
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 99-101: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yaml around lines 99 - 101, In the npm job's
checkout step (the actions/checkout@... invocation that currently sets
fetch-depth: 0), add persist-credentials: false under the with: block so GitHub
Actions will not persist repo credentials to the workspace; update the with
block alongside fetch-depth to include persist-credentials: false to reduce
credential exposure when only downloading artifacts and publishing packages.

Source: Linters/SAST tools


103-106: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add package-manager-cache: false to prevent cache poisoning.

The actions/setup-node action enables caching by default when package.json contains a packageManager field, creating a cache poisoning risk in privileged workflows. Since this npm job has id-token: write and publishes packages, explicitly disable the package manager cache.

🛡️ Proposed fix
       - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
         with:
           registry-url: 'https://registry.npmjs.org'
           node-version: lts/*
+          package-manager-cache: false
🧰 Tools
🪛 zizmor (1.25.2)

[error] 103-103: runtime artifacts potentially vulnerable to a cache poisoning attack (cache-poisoning): enables caching by default

(cache-poisoning)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yaml around lines 103 - 106, The setup-node step
currently configures registry-url and node-version but leaves package manager
caching enabled; update the actions/setup-node step (the one containing
registry-url and node-version: lts/*) to explicitly disable package manager
caching by adding package-manager-cache: false under its with block so the
action will not create a package manager cache in this privileged workflow
(which also uses id-token: write).

Source: Linters/SAST tools

@cubic-dev-ai cubic-dev-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.

3 issues found across 11 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".github/workflows/release.yaml">

<violation number="1" location=".github/workflows/release.yaml:152">
P1: `pnpm publish` is executed with a pnpm version pinned to 11.1.1, which is affected by known OIDC trusted-publishing auth issues with setup-node-generated npm auth config; release publishes can fail.</violation>
</file>

<file name=".github/workflows/pr-gate.yaml">

<violation number="1" location=".github/workflows/pr-gate.yaml:4">
P1: Switching this workflow to `pull_request` can break PR auto-close enforcement for forked contributions because the token is read-only there.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic


on:
pull_request_target:
pull_request:

@cubic-dev-ai cubic-dev-ai Bot Jun 10, 2026 •

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.

P1: Switching this workflow to pull_request can break PR auto-close enforcement for forked contributions because the token is read-only there.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/pr-gate.yaml, line 4:

<comment>Switching this workflow to `pull_request` can break PR auto-close enforcement for forked contributions because the token is read-only there.</comment>

<file context>
@@ -1,13 +1,13 @@
 
 on:
-  pull_request_target:
+  pull_request:
     types:
       - opened
</file context>
Suggested change
pull_request:
pull_request_target:
Fix with cubic

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

pull_request_target provides security incident recently. you should not propose this dangerous 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.

Got it — I won’t suggest that change for this workflow.

Comment thread .github/workflows/release.yaml
Comment thread .github/actionlint.yaml Outdated
Remove the unsupported commit input from the pinned useblacksmith/stickydisk action and recreate /nix/store after the sticky disk is mounted before fixing ownership. The mounted filesystem can replace the pre-created directory tree, so /nix/store must be created after mount to avoid Linux ARM setup failures.

Drop the remaining actions/cache based Cargo cache from Windows native package builds. The Blacksmith sticky disk action is documented as an ext4 mounted disk and the pinned implementation shells out to mkfs.ext4 and mount, so Windows native runners cannot safely use it as a direct replacement.

Restore action-timeline to the main-branch ubuntu-slim runner because that reporting job does not need Blacksmith capacity.
Restore the lightweight PR gate, issue gate, contributor approval, PR title, code-change detection, and action timeline jobs to the GitHub-hosted ubuntu-slim runner. These jobs only run small GitHub API or reporting work and do not benefit from Blacksmith capacity.

Keep the actionlint Blacksmith runner allow-list limited to the heavy runners that remain in use after moving lightweight jobs back to ubuntu-slim.
@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Disable persisted checkout credentials in the npm publish and release jobs, and disable setup-node package manager caching in the npm publish job. This keeps the existing GitHub-hosted OIDC publish flow intact while addressing CodeRabbit credential and cache-poisoning findings.

Remove the global SC2016 actionlint suppression and scope the shellcheck directive to the two inline Node.js command substitutions that intentionally use single-quoted JavaScript.
@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Restore actions/cache for Windows Cargo builds in CI and release. Blacksmith documentation says official GitHub cache actions transparently use Blacksmith colocated cache on Blacksmith runners and recommends upstream cache actions over archived useblacksmith cache forks.

Do not use useblacksmith/stickydisk for Windows Cargo because the sticky disk documentation describes ext4 filesystem mounts and the pinned action implementation formats and mounts ext4 block devices. Nix store caching remains on sticky disk as requested.
@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cubic-dev-ai cubic-dev-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.

1 issue found and verified against the latest diff

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".github/workflows/cache-gc.yaml">

<violation number="1" location=".github/workflows/cache-gc.yaml:8">
P3: Workflow token is over-permissioned with `read-all`; scope it to only required permissions.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

workflow_dispatch:

permissions: {}
permissions: read-all

@cubic-dev-ai cubic-dev-ai Bot Jun 10, 2026 •

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.

P3: Workflow token is over-permissioned with read-all; scope it to only required permissions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/cache-gc.yaml, line 8:

<comment>Workflow token is over-permissioned with `read-all`; scope it to only required permissions.</comment>

<file context>
@@ -1,39 +1,30 @@
   workflow_dispatch:
 
-permissions: {}
+permissions: read-all
 
 jobs:
</file context>
Suggested change
permissions: read-all
permissions:
contents: read
Fix with cubic

Replace the macOS build action input switch with separate Nix and Cargo composite actions.

The CI macOS ARM build continues to use the Nix action, release macOS ARM uses the same path, and release Intel macOS calls the Cargo action so it avoids slow Nix setup and cache behavior on the GitHub-hosted Intel runner.
@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
.github/workflows/ci.yaml (1)

120-120: Update: nix-github-token is a boolean-like gating flag (not a token value).

The composite action’s nix-github-token input is defined as “Whether Nix builds may use the GitHub token from Nix config” and is compared to the string "false", so passing ${{ github.event_name != 'pull_request' }} is appropriate. Renaming it for clarity (e.g., enable-nix-github-token) would be optional.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yaml at line 120, The input name `nix-github-token` is
a boolean-like gating flag (not an actual token) and is being compared to the
string "false", so update the workflow to make intent clear: keep passing the
expression `${{ github.event_name != 'pull_request' }}` to the composite action
input `nix-github-token` (or optionally rename the input to something clearer
like `enable-nix-github-token` in the composite action metadata and all call
sites), and ensure any string comparisons against `"false"` in the composite
action logic (e.g., checks inside the composite that reference
`nix-github-token`) continue to handle the boolean-string value consistently.
.github/actions/build-linux-native-package/action.yaml (1)

27-37: 💤 Low value

Simplify: Both branches execute identical verification logic.

The if/else branches on Line 31-37 run the same cat and grep commands regardless of ldd's exit status. You can simplify by capturing output unconditionally:

♻️ Suggested refactor
     - name: Verify Linux binary is static
       shell: bash
       run: |
         file "${{ inputs.binary }}"
-        if ldd "${{ inputs.binary }}" > "$RUNNER_TEMP/ldd.txt" 2>&1; then
-          cat "$RUNNER_TEMP/ldd.txt"
-          grep -Eiq 'not a dynamic executable|statically linked' "$RUNNER_TEMP/ldd.txt"
-        else
-          cat "$RUNNER_TEMP/ldd.txt"
-          grep -Eiq 'not a dynamic executable|statically linked' "$RUNNER_TEMP/ldd.txt"
-        fi
+        ldd "${{ inputs.binary }}" > "$RUNNER_TEMP/ldd.txt" 2>&1 || true
+        cat "$RUNNER_TEMP/ldd.txt"
+        grep -Eiq 'not a dynamic executable|statically linked' "$RUNNER_TEMP/ldd.txt"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/actions/build-linux-native-package/action.yaml around lines 27 - 37,
The step "Verify Linux binary is static" contains an if/else whose bodies are
identical — both branches run `cat "$RUNNER_TEMP/ldd.txt"` and `grep -Eiq 'not a
dynamic executable|statically linked' "$RUNNER_TEMP/ldd.txt"` after calling
`ldd` on `${{ inputs.binary }}`; simplify by running `ldd "${{ inputs.binary }}"
> "$RUNNER_TEMP/ldd.txt" 2>&1` unconditionally and then always executing the
`cat` and `grep` commands (remove the duplicated if/else), keeping the use of
`$RUNNER_TEMP/ldd.txt` and the same grep pattern to preserve behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/actions/build-macos-native-package/action.yaml:
- Around line 15-17: Add a post-build verification step after the "Build macOS
binary" step that inspects the built binary(s) for any remaining /nix/store
paths and fails the job if any are found: locate the produced artifact from the
nix build (the output referenced by .#ccusage), run the macOS linker inspection
(otool -L) or string search against that binary, and assert that the output
contains no "/nix/store" entries; name the step "Verify no /nix/store
references" and ensure it returns non-zero on detection so install_name_tool
rewriting is effectively validated.

In @.github/workflows/ci.yaml:
- Line 114: In the build-native-packages job's checkout step that uses
actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10, add the configuration
key persist-credentials: false under the step's with: block to prevent GitHub
Actions from persisting PATs/credentials into the workspace and potentially
leaking them into uploaded artifacts; ensure the change mirrors other checkout
steps that already use persist-credentials: false.

In @.github/workflows/release.yaml:
- Around line 9-12: The build-native-packages job currently inherits default
token permissions; add an explicit permissions block under the
build-native-packages job to harden the workflow by specifying only the minimal
scopes needed (for example add a permissions map like "contents: read" and any
other specific scopes your job requires such as "packages: write" or "id-token:
write" if you publish or authenticate). Locate the build-native-packages job and
insert the permissions: section with only the required keys and values to avoid
broad default access.

---

Nitpick comments:
In @.github/actions/build-linux-native-package/action.yaml:
- Around line 27-37: The step "Verify Linux binary is static" contains an
if/else whose bodies are identical — both branches run `cat
"$RUNNER_TEMP/ldd.txt"` and `grep -Eiq 'not a dynamic executable|statically
linked' "$RUNNER_TEMP/ldd.txt"` after calling `ldd` on `${{ inputs.binary }}`;
simplify by running `ldd "${{ inputs.binary }}" > "$RUNNER_TEMP/ldd.txt" 2>&1`
unconditionally and then always executing the `cat` and `grep` commands (remove
the duplicated if/else), keeping the use of `$RUNNER_TEMP/ldd.txt` and the same
grep pattern to preserve behavior.

In @.github/workflows/ci.yaml:
- Line 120: The input name `nix-github-token` is a boolean-like gating flag (not
an actual token) and is being compared to the string "false", so update the
workflow to make intent clear: keep passing the expression `${{
github.event_name != 'pull_request' }}` to the composite action input
`nix-github-token` (or optionally rename the input to something clearer like
`enable-nix-github-token` in the composite action metadata and all call sites),
and ensure any string comparisons against `"false"` in the composite action
logic (e.g., checks inside the composite that reference `nix-github-token`)
continue to handle the boolean-string value consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 09ef0f2a-7e78-4d82-b68f-5c9e45a065d6

📥 Commits

Reviewing files that changed from the base of the PR and between b4e625e and 6798515.

📒 Files selected for processing (9)
  • .github/actions/build-linux-native-package/action.yaml
  • .github/actions/build-macos-native-package/action.yaml
  • .github/actions/build-windows-native-package/action.yaml
  • .github/actions/setup-linux-blacksmith-sticky-disk/action.yaml
  • .github/actions/setup-macos-nix-cache/action.yaml
  • .github/actions/setup-native-build-cache/action.yaml
  • .github/actions/setup-nix/action.yaml
  • .github/workflows/ci.yaml
  • .github/workflows/release.yaml
✅ Files skipped from review due to trivial changes (2)
  • .github/actions/setup-native-build-cache/action.yaml
  • .github/actions/setup-macos-nix-cache/action.yaml

Comment on lines +15 to +17
- name: Build macOS binary
shell: bash
run: nix build .#ccusage

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add verification step to ensure no /nix/store references in the binary.

Issue #1251 explicitly requests a CI check to verify macOS binaries are free of /nix/store paths. The Linux action includes static-linkage verification; the macOS action should similarly verify that install_name_tool successfully rewrote library paths.

✅ Suggested verification step to add after the build
     - name: Build macOS binary
       shell: bash
       run: nix build .#ccusage

+    - name: Verify macOS binary has no /nix/store references
+      shell: bash
+      run: |
+        echo "Checking dynamic library dependencies..."
+        otool -L "${{ inputs.binary }}"
+        if otool -L "${{ inputs.binary }}" | grep -q '/nix/store'; then
+          echo "Error: Binary contains /nix/store references"
+          exit 1
+        fi
+        echo "✓ No /nix/store references found"
+
     - name: Stage native package
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Build macOS binary
shell: bash
run: nix build .#ccusage
- name: Build macOS binary
shell: bash
run: nix build .#ccusage
- name: Verify macOS binary has no /nix/store references
shell: bash
run: |
echo "Checking dynamic library dependencies..."
otool -L "${{ inputs.binary }}"
if otool -L "${{ inputs.binary }}" | grep -q '/nix/store'; then
echo "Error: Binary contains /nix/store references"
exit 1
fi
echo "✓ No /nix/store references found"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/actions/build-macos-native-package/action.yaml around lines 15 - 17,
Add a post-build verification step after the "Build macOS binary" step that
inspects the built binary(s) for any remaining /nix/store paths and fails the
job if any are found: locate the produced artifact from the nix build (the
output referenced by .#ccusage), run the macOS linker inspection (otool -L) or
string search against that binary, and assert that the output contains no
"/nix/store" entries; name the step "Verify no /nix/store references" and ensure
it returns non-zero on detection so install_name_tool rewriting is effectively
validated.

Comment thread .github/workflows/ci.yaml
binary: rust/target/release/ccusage.exe

steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Set persist-credentials: false to prevent credential leakage.

The checkout step in the build-native-packages job does not disable credential persistence. Since this job uploads artifacts, persisted credentials in the working directory could leak into the uploaded packages. Other checkout steps in this workflow (lines 19, 48, 221, 293) correctly set persist-credentials: false.

🔒 Proposed fix
       - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
+        with:
+          persist-credentials: false
       - uses: ./.github/actions/build-linux-native-package
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 114-114: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yaml at line 114, In the build-native-packages job's
checkout step that uses
actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10, add the configuration
key persist-credentials: false under the step's with: block to prevent GitHub
Actions from persisting PATs/credentials into the workspace and potentially
leaking them into uploaded artifacts; ensure the change mirrors other checkout
steps that already use persist-credentials: false.

Source: Linters/SAST tools

Comment on lines 9 to 12
build-native-packages:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
timeout-minutes: 30

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Set explicit minimal permissions for security hardening.

The build-native-packages job inherits default token permissions because no explicit permissions: block is defined. This widens the attack surface unnecessarily.

🛡️ Proposed fix
 jobs:
   build-native-packages:
     name: ${{ matrix.name }}
     runs-on: ${{ matrix.os }}
     timeout-minutes: 30
+    permissions:
+      contents: read
     strategy:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
build-native-packages:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
timeout-minutes: 30
jobs:
build-native-packages:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
timeout-minutes: 30
permissions:
contents: read
strategy:
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 9-67: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yaml around lines 9 - 12, The
build-native-packages job currently inherits default token permissions; add an
explicit permissions block under the build-native-packages job to harden the
workflow by specifying only the minimal scopes needed (for example add a
permissions map like "contents: read" and any other specific scopes your job
requires such as "packages: write" or "id-token: write" if you publish or
authenticate). Locate the build-native-packages job and insert the permissions:
section with only the required keys and values to avoid broad default access.

Source: Linters/SAST tools

Pass composite action inputs through environment variables before shell execution so zizmor does not treat arch and binary values as attacker-controllable template injection.
@ryoppippi

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 6798515592ad
Base SHA: caab151793d8

This compares the PR package against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new caab151793d8 766.2ms 926.3ms 55.7ms 3
PR pkg.pr.new 6798515 909.2ms 1.336s 50.4ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: caab151793d8; PR package: 6798515. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 1.002s 940.3ms 1.07x 726.75 MiB 730.75 MiB 1.01x 1.00 GiB/s 1.07 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 195.3ms 192.7ms 1.01x 90.75 MiB 92.25 MiB 1.02x 5.15 GiB/s 5.22 GiB/s

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 910.3ms 1.11 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 933.2ms 1.08 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 177.7ms 5.67 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 139.1ms 7.24 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude daily --offline --json 0.00 MiB 43.2ms 48.2ms 0.90x 43.75 MiB 43.50 MiB 0.99x 0.04 MiB/s 0.03 MiB/s
claude session --offline --json 0.00 MiB 46.8ms 48.2ms 0.97x 43.75 MiB 43.50 MiB 0.99x 0.03 MiB/s 0.03 MiB/s
codex daily --offline --json 0.00 MiB 43.2ms 42.1ms 1.03x 43.75 MiB 43.75 MiB 1.00x 0.02 MiB/s 0.02 MiB/s
codex session --offline --json 0.00 MiB 43.2ms 42.4ms 1.02x 43.50 MiB 43.50 MiB 1.00x 0.02 MiB/s 0.02 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude --offline --json 1.01 GiB 924.3ms 855.3ms 1.08x 733.00 MiB 740.25 MiB 1.01x 1.09 GiB/s 1.18 GiB/s
codex --offline --json 1.01 GiB 181.8ms 189.3ms 0.96x 91.75 MiB 90.25 MiB 0.98x 5.54 GiB/s 5.32 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 17.30 KiB 17.30 KiB +0.00 KiB 1.00x
installed native package binary 3417.74 KiB 3417.74 KiB +0.00 KiB 1.00x

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 6798515592ad
Base SHA: caab151793d8

This compares the Rust PR release binary against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new caab151793d8 689.8ms 929.8ms 49.6ms 3
PR pkg.pr.new 6798515 735.8ms 728.5ms 50.0ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: caab151793d8; PR package: 6798515. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 902.5ms 873.3ms 1.03x 728.75 MiB 748.50 MiB 1.03x 1.12 GiB/s 1.15 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 174.2ms 178.0ms 0.98x 93.25 MiB 88.50 MiB 0.95x 5.78 GiB/s 5.65 GiB/s

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 901.5ms 1.12 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 817.0ms 1.23 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 165.4ms 6.09 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 123.7ms 8.14 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude daily --offline --json 0.00 MiB 45.0ms 6.1ms 7.42x 45.50 MiB 2.50 MiB 0.05x 0.03 MiB/s 0.25 MiB/s
claude session --offline --json 0.00 MiB 45.0ms 6.0ms 7.45x 45.75 MiB 2.50 MiB 0.05x 0.03 MiB/s 0.26 MiB/s
codex daily --offline --json 0.00 MiB 44.7ms 5.6ms 8.04x 45.50 MiB 2.50 MiB 0.05x 0.02 MiB/s 0.15 MiB/s
codex session --offline --json 0.00 MiB 44.0ms 5.5ms 8.06x 45.50 MiB 2.50 MiB 0.05x 0.02 MiB/s 0.16 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude --offline --json 1.01 GiB 914.4ms 852.5ms 1.07x 736.50 MiB 735.25 MiB 1.00x 1.10 GiB/s 1.18 GiB/s
codex --offline --json 1.01 GiB 160.7ms 124.5ms 1.29x 92.25 MiB 91.50 MiB 0.99x 6.27 GiB/s 8.08 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 17.30 KiB 17.30 KiB +0.00 KiB 1.00x
installed native package binary 3417.74 KiB 3417.74 KiB +0.00 KiB 1.00x

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@ryoppippi
ryoppippi merged commit b47a31a into main Jun 10, 2026
20 of 21 checks passed
@ryoppippi
ryoppippi deleted the codex/migrate-blacksmith-ci branch June 10, 2026 20:07
@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 063367690674
Base SHA: caab151793d8

This compares the Rust PR release binary against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new caab151793d8 832.4ms 1.621s 59.2ms 3
PR pkg.pr.new 0633676 836.2ms 868.9ms 50.0ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: caab151793d8; PR package: 0633676. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 896.6ms 916.2ms 0.98x 738.25 MiB 744.50 MiB 1.01x 1.12 GiB/s 1.10 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 206.4ms 198.7ms 1.04x 89.25 MiB 91.25 MiB 1.02x 4.88 GiB/s 5.07 GiB/s

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 958.7ms 1.05 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 911.3ms 1.10 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 183.8ms 5.48 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 144.3ms 6.98 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude daily --offline --json 0.00 MiB 46.3ms 6.9ms 6.68x 43.75 MiB 2.50 MiB 0.06x 0.03 MiB/s 0.22 MiB/s
claude session --offline --json 0.00 MiB 42.8ms 6.5ms 6.58x 43.50 MiB 2.75 MiB 0.06x 0.04 MiB/s 0.24 MiB/s
codex daily --offline --json 0.00 MiB 43.0ms 6.5ms 6.61x 43.50 MiB 2.50 MiB 0.06x 0.02 MiB/s 0.13 MiB/s
codex session --offline --json 0.00 MiB 41.9ms 6.3ms 6.61x 43.00 MiB 2.50 MiB 0.06x 0.02 MiB/s 0.14 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude --offline --json 1.01 GiB 940.5ms 921.7ms 1.02x 724.25 MiB 738.50 MiB 1.02x 1.07 GiB/s 1.09 GiB/s
codex --offline --json 1.01 GiB 180.4ms 141.6ms 1.27x 90.75 MiB 92.00 MiB 1.01x 5.58 GiB/s 7.11 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 17.30 KiB 17.30 KiB -0.00 KiB 1.00x
installed native package binary 3417.74 KiB 3417.74 KiB +0.00 KiB 1.00x

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 063367690674
Base SHA: caab151793d8

This compares the PR package against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new caab151793d8 905.9ms 1.347s 67.2ms 3
PR pkg.pr.new 0633676 873.5ms 931.1ms 63.3ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: caab151793d8; PR package: 0633676. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 1.018s 1.054s 0.97x 738.50 MiB 726.25 MiB 0.98x 1013.17 MiB/s 978.32 MiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 232.0ms 231.2ms 1.00x 95.75 MiB 94.00 MiB 0.98x 4.34 GiB/s 4.35 GiB/s

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 1.002s 1.01 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 939.9ms 1.07 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 197.9ms 5.09 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 175.4ms 5.74 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude daily --offline --json 0.00 MiB 51.8ms 51.8ms 1.00x 43.75 MiB 43.75 MiB 1.00x 0.03 MiB/s 0.03 MiB/s
claude session --offline --json 0.00 MiB 52.3ms 55.2ms 0.95x 43.50 MiB 43.75 MiB 1.01x 0.03 MiB/s 0.03 MiB/s
codex daily --offline --json 0.00 MiB 52.9ms 50.4ms 1.05x 43.50 MiB 43.50 MiB 1.00x 0.02 MiB/s 0.02 MiB/s
codex session --offline --json 0.00 MiB 53.2ms 50.8ms 1.05x 43.50 MiB 43.50 MiB 1.00x 0.02 MiB/s 0.02 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude --offline --json 1.01 GiB 982.8ms 928.5ms 1.06x 728.75 MiB 742.00 MiB 1.02x 1.02 GiB/s 1.08 GiB/s
codex --offline --json 1.01 GiB 198.3ms 199.0ms 1.00x 93.25 MiB 94.00 MiB 1.01x 5.08 GiB/s 5.06 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 17.30 KiB 17.30 KiB -0.00 KiB 1.00x
installed native package binary 3417.74 KiB 3417.74 KiB +0.00 KiB 1.00x

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: f8a302db93c7
Base SHA: caab151793d8

This compares the Rust PR release binary against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new caab151793d8 660.8ms 1.358s 48.2ms 3
PR pkg.pr.new f8a302d 722.4ms 968.8ms 50.4ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: caab151793d8; PR package: f8a302d. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 890.3ms 970.4ms 0.92x 727.75 MiB 725.50 MiB 1.00x 1.13 GiB/s 1.04 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 188.5ms 191.1ms 0.99x 91.00 MiB 87.25 MiB 0.96x 5.34 GiB/s 5.27 GiB/s

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 960.3ms 1.05 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 902.6ms 1.12 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 179.9ms 5.59 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 139.4ms 7.22 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude daily --offline --json 0.00 MiB 40.4ms 6.7ms 6.04x 43.50 MiB 2.50 MiB 0.06x 0.04 MiB/s 0.23 MiB/s
claude session --offline --json 0.00 MiB 40.6ms 6.7ms 6.04x 43.50 MiB 2.50 MiB 0.06x 0.04 MiB/s 0.23 MiB/s
codex daily --offline --json 0.00 MiB 40.4ms 6.3ms 6.42x 43.50 MiB 2.50 MiB 0.06x 0.02 MiB/s 0.14 MiB/s
codex session --offline --json 0.00 MiB 40.4ms 6.5ms 6.26x 43.50 MiB 2.50 MiB 0.06x 0.02 MiB/s 0.13 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs rust/target/release/ccusage directly. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude --offline --json 1.01 GiB 892.2ms 841.5ms 1.06x 738.50 MiB 724.75 MiB 0.98x 1.13 GiB/s 1.20 GiB/s
codex --offline --json 1.01 GiB 178.7ms 139.0ms 1.29x 92.75 MiB 95.00 MiB 1.02x 5.63 GiB/s 7.24 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 17.30 KiB 17.30 KiB -0.00 KiB 1.00x
installed native package binary 3417.74 KiB 3417.74 KiB +0.00 KiB 1.00x

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: f8a302db93c7
Base SHA: caab151793d8

This compares the PR package against the configured base package on the same CI runner.

Package runner startup

Execution setup measures any pre-benchmark package materialization used by the execution benchmark. Bunx temp cache measures one bunx -p <url> ccusage --version run with an empty Bun install cache. Warm reuses that cache and reports the median of repeated runs.

Package SHA Execution setup Bunx temp cache Bunx warm median Warm samples
Base pkg.pr.new caab151793d8 836.4ms 959.6ms 61.0ms 3
PR pkg.pr.new f8a302d 880.4ms 981.7ms 62.5ms 3

Cached bunx execution performance

Runs the same large fixture through bunx -p <pkg.pr.new URL> ccusage after the Bun install cache has already been populated by the startup measurement. This separates cached package-runner execution from first-fetch package materialization.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base package: caab151793d8; PR package: f8a302d. Both run through bunx -p <pkg.pr.new URL> ccusage using the warmed Bun install cache from package runner startup, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
bunx -p <pkg> ccusage claude --offline --json 1.01 GiB 1.012s 1.049s 0.96x 725.25 MiB 746.00 MiB 1.03x 1019.18 MiB/s 983.06 MiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 208.5ms 205.9ms 1.01x 89.25 MiB 91.50 MiB 1.03x 4.83 GiB/s 4.89 GiB/s

Package runtime diagnostics

Compares the PR package wrapper, the installed native optional dependency binary, and the workspace release binary on the same large fixture. This identifies whether slow package results come from JavaScript wrapper overhead, the published native binary build, or the Rust core itself.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
All rows run --offline --json, measured by hyperfine with 0 warmups and 1 runs. This isolates wrapper overhead from the installed native optional dependency and the workspace release binary built on the runner.

Command Runtime Input Median Throughput Samples
claude --offline --json Package wrapper 1.01 GiB 1.076s 958.54 MiB/s 1
claude --offline --json Installed native binary 1.01 GiB 1.045s 986.53 MiB/s 1
codex --offline --json Package wrapper 1.01 GiB 207.0ms 4.86 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 153.7ms 6.55 GiB/s 1

Committed fixture performance

Committed small fixtures for stable PR-to-PR feedback and explicit Claude/Codex command coverage.

Fixtures: Claude apps/ccusage/test/fixtures/claude (0.00 MiB, 2 files), Codex apps/ccusage/test/fixtures/codex (0.00 MiB, 1 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 2 warmups and 7 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude daily --offline --json 0.00 MiB 51.1ms 49.0ms 1.04x 43.75 MiB 43.75 MiB 1.00x 0.03 MiB/s 0.03 MiB/s
claude session --offline --json 0.00 MiB 52.1ms 51.2ms 1.02x 43.75 MiB 43.75 MiB 1.00x 0.03 MiB/s 0.03 MiB/s
codex daily --offline --json 0.00 MiB 50.5ms 50.5ms 1.00x 43.50 MiB 43.50 MiB 1.00x 0.02 MiB/s 0.02 MiB/s
codex session --offline --json 0.00 MiB 50.9ms 51.2ms 0.99x 43.50 MiB 43.75 MiB 1.01x 0.02 MiB/s 0.02 MiB/s

Large real-world-shaped fixture performance

Generated fixtures shaped from aggregate local log statistics: thousands of JSONL files, many small sessions, and a long tail of larger sessions. No real prompts, paths, or outputs are stored in the fixtures.

Fixtures: Claude /home/runner/_work/_temp/ccusage-large-fixture (1.01 GiB, 2,597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2,597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published ccusage package from pkg.pr.new, installed before measurement. Both run --offline --json, measured by hyperfine with 0 warmups and 1 runs.
Peak RSS is measured separately with /usr/bin/time using 1 runs. Lower RSS ratios are better.

Command Input Base median PR median PR vs base Base peak RSS PR peak RSS PR/base RSS Base throughput PR throughput
claude --offline --json 1.01 GiB 1.095s 990.3ms 1.11x 725.50 MiB 741.00 MiB 1.02x 941.65 MiB/s 1.02 GiB/s
codex --offline --json 1.01 GiB 180.2ms 192.4ms 0.94x 90.00 MiB 92.25 MiB 1.02x 5.59 GiB/s 5.23 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 17.30 KiB 17.30 KiB -0.00 KiB 1.00x
installed native package binary 3417.74 KiB 3417.74 KiB +0.00 KiB 1.00x

Lower medians and smaller artifacts are better. CI runner noise still applies; use same-run ratios as directional PR feedback, not release guarantees.

ryoppippi added a commit that referenced this pull request Jun 10, 2026
* refactor(nix): merge development flake back into the root flake

Fold dev/flake.nix back into flake.nix so the repository ships a single
flake again: treefmt-nix, git-hooks, and agent-skills inputs return to
the root flake, and the development modules (treefmt, git-hooks,
dev-shell, agent-skills) are imported alongside the package modules.

The dev/ flake split (#1243) existed to keep package-only CI builds from
evaluating development inputs, trading a simpler setup for speed. With
the Blacksmith sticky-disk Nix store cache (#1249) the extra evaluation
cost no longer matters, so the simpler single-flake layout wins.

The pins for the restored inputs (agent-skills, git-hooks, treefmt-nix
and their transitive deps) are carried over verbatim from dev/flake.lock,
so no input revisions change. The dev shell is reached with plain
'nix develop' / 'use flake' again, and all './dev#' flake references
move back to the root flake.

* refactor(ci): drop the ci dev shell and explicit pnpm install steps

Remove devShells.ci and the just install / flake-check recipes that
existed to keep CI shells free of side effects. Every workflow now
enters the default dev shell with plain 'nix develop --command', whose
shellHook installs pnpm dependencies, syncs agent skills, and installs
git hooks.

The shellHook now runs 'pnpm install --frozen-lockfile' unconditionally
instead of comparing lockfile mtimes: an up-to-date install takes about
a second, and the conditional occasionally kept stale node_modules
around after branch switches.

Also drop the warm-dev-shell input from the setup-nix action (nothing
passed it any more) and restore the direct 'nix flake check
--print-build-logs' step in the lint job. The separate ci shell and
explicit install steps were a #1243-era optimisation to avoid paying
the shellHook cost in CI; with sticky-disk caching the cost is noise
and the extra indirection just made the setup harder to follow.

* ci: report what the Nix and node_modules caches restored

The sticky disk and cache-nix-action steps only log that a mount or
restore happened, so job logs never showed whether the run started warm
or cold, or what the restored data actually was.

Add report steps after each restore that print the sticky disk key, the
mount path, disk usage, and the number of restored Nix store paths or
top-level node_modules entries, with an explicit cold-start message when
the disk or cache comes back empty.

* Revert "ci: report what the Nix and node_modules caches restored"

This reverts commit af47cba.

* Update nix/dev-shell.nix

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* chore(ci): update github actions

* ci: simplify pnpm version resolution for the sticky disk key

Read the packageManager pin with jq instead of a node -p invocation and
a case statement. The resolved version only feeds the node_modules
sticky disk cache key; the pnpm that actually runs self-resolves to the
pinned version via manage-package-manager-versions regardless of which
binary starts it.

* ci: stop caching node_modules on a sticky disk

Drop the root node_modules sticky disk and the pnpm version resolution
that only existed to build its cache key. A warm disk saved roughly
twenty seconds of pnpm install, while mounting cost five to forty-five
seconds per job (and occasionally failed outright), every job committed
a snapshot even when nothing ran pnpm, and the per-GB-billed disk grew
to tens of gigabytes despite node_modules holding about 500 MB of
files. Installing from the registry on each run is cheaper and simpler.

The Nix store sticky disk stays; that one caches multi-gigabyte build
closures that are genuinely expensive to recreate.

---------

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
ryoppippi added a commit that referenced this pull request Jun 10, 2026
Blacksmith is a new sponsor of ccusage; the repository CI was
migrated to Blacksmith runners in #1249. Add the Blacksmith logo
to docs/public and show it alongside the CodeRabbit logo in the
README sponsor section, the docs home page sponsor block, and the
sponsors guide. The logo ships with its own yellow background, so
a dark-mode variant is not needed.
ryoppippi added a commit that referenced this pull request Jun 10, 2026
* build(ccusage): reject non-portable staged native binaries

ensure-native-binary.ts accepted any staged package binary whose
version matched, including binaries dynamically linked against
libraries that only exist on the build machine. A Nix-built macOS
binary linking /nix/store/.../libiconv.2.dylib shipped this way and
crashed for users without Nix with a missing dynamic library error.

Add an isPortableBinary() gate before accepting the staged binary:
Linux binaries must be fully static (ldd, mirroring the release CI
check), macOS binaries may only link system dylibs under /usr/lib or
/System/Library (otool -L), and Windows is not checked because MSVC
builds link only system DLLs by default. A version-matched but
non-portable binary now fails loudly instead of being silently
accepted or silently replaced by a local cargo build.

* fix(nix): fail the darwin build when non-system dylibs remain

The darwin package relies on install_name_tool to rewrite the Nix
store libiconv reference to /usr/lib/libiconv.2.dylib (#1249), but
install_name_tool -change is a silent no-op when the old path does
not match, and nothing verified the final binary. A regression (for
example a new Nix-linked dylib) would ship unnoticed and crash for
users without /nix/store.

Assert in postInstall that every linked dylib lives under /usr/lib
or /System/Library, printing the offending entries otherwise. The
assertion runs on every nix build and via nix flake check, which
already builds the ccusage package as a check.

Verified both ways on darwin: the current build passes, and removing
the install_name_tool line makes the build fail with the offending
/nix/store path in the log.

* fix(nix): assert the static Linux build has no dynamic loader

ccusage-static targets musl specifically so the published Linux
binaries run without any dynamic libraries, but the derivation never
verified the result; only a separate CI action step did. Check in
postInstall that the binary has no PT_INTERP program header, which
would mean it requests a dynamic loader and fails on end-user
machines. READELF comes from the cross bintools wrapper, with a
plain readelf fallback.

This complements the existing ldd verification in
build-linux-native-package, moving the primary gate into nix build
itself so local builds and any future workflows are covered too.

* ci: verify the cargo-built macOS x64 binary links only system dylibs

The darwin-x64 package is built with plain cargo on a GitHub macOS
runner, so the portability assertion added to the Nix derivation does
not cover it and no other gate inspects it before staging. Fail the
build action if otool -L reports any dylib outside /usr/lib or
/System/Library, mirroring the assertion in package.nix.

The check logic was exercised locally on macOS: a /usr/lib-linked
binary passes and a Nix-store-linked binary fails with the offending
path printed.
ryoppippi added a commit that referenced this pull request Jun 11, 2026
The Blacksmith migration (#1249) changed the PR Gate trigger from
`pull_request_target` to `pull_request`. For PRs opened by outside
contributors, `pull_request` workflows are gated as `action_required`
and do not run without a maintainer manually approving them, so the
auto-close gate silently stopped firing (e.g. #1281 stayed open).

Restore `pull_request_target` so the gate runs with the base repo's
write permissions and can close unapproved PRs automatically. This
workflow never checks out PR code — it only calls the GitHub API via
github-script — so the usual pull_request_target injection risk does
not apply. Added an inline comment documenting the requirement to
prevent the same regression in future workflow edits.
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.

darwin binary fails on non-Nix Macs: dyld can't load libiconv.2.dylib from hardcoded /nix/store path (v20.0.10)

1 participant