Skip to content

fix(nix): strip the unused libiconv dependency on Darwin - #1292

Merged
ryoppippi merged 3 commits into
mainfrom
fix/drop-unused-libiconv
Jun 11, 2026
Merged

ryoppippi merged 3 commits into
mainfrom
fix/drop-unused-libiconv

Conversation

@ryoppippi

@ryoppippi ryoppippi commented Jun 11, 2026 •

Copy link
Copy Markdown
Member

Summary

ccusage references no iconv symbols (nm -u on both arches, no iconv crate),
yet the Darwin binary recorded libiconv.2.dylib as an unused load-time
dependency. Built with Nix that path was the /nix/store one, which dyld
can't resolve on non-Nix Macs — the crash behind #1251 (worked around in #1256
by rewriting the install name).

Root cause (corrected)

Removing libiconv from buildInputs is not enough — the nixpkgs Darwin
stdenv injects -liconv regardless, so the unused dylib is still recorded.
dyld loads every recorded dylib at launch whether or not its symbols are used,
so an unused dep with an unresolvable path aborts the process before main.

Fix

Add -Wl,-dead_strip_dylibs to the Darwin link, which drops dylib load commands
with no referenced symbols. The unused libiconv is removed and the binary links
only /usr/lib/libSystem.B.dylib (verified locally on arm64). The #1256
install-name rewrite is no longer needed; the otool portability gate stays.

Linux ships a fully static musl binary (no dynamic deps), so no equivalent
(--as-needed) is needed there.

Validation

  • Local arm64: otool -L → only /usr/lib/libSystem.B.dylib.
  • The non-Nix preview E2E (arm64 mac + macos-15-intel) is the authoritative
    runtime check.

Summary by CodeRabbit

  • Chores
    • Removed an unnecessary macOS library input to simplify Darwin builds.
    • Added a Darwin linker flag to avoid recording unused dynamic libraries.
    • Made post-install rewriting robust: any bundled libiconv entries are remapped to the system libiconv as a fallback.
    • Kept the verification that fails builds if non-system dynamic libraries remain.

ccusage links no iconv symbols (verified: `nm -u` shows none on both arm64 and
x64, no iconv crate in Cargo.lock), but libiconv was listed in the Darwin
buildInputs, so the linker recorded /usr/lib/libiconv.2.dylib as an unused
load-time dependency. When the Darwin package was built with Nix that path was
the /nix/store one, which dyld can't resolve on non-Nix Macs — the crash behind
#1251 (worked around in #1256 by rewriting the install name).

Remove libiconv from buildInputs so the binary links only system dylibs and the
install-name rewrite is no longer needed. The portability gate (otool, reject
non-system dylibs) stays to catch any future stray /nix dependency.
@coderabbitai

coderabbitai Bot commented Jun 11, 2026 •

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fbeb858f-2417-4cb5-b603-2ba1d73b2298

📥 Commits

Reviewing files that changed from the base of the PR and between 687df0c and 6da7e0c.

📒 Files selected for processing (2)
  • nix/darwin-x64-package.nix
  • package.nix

📝 Walkthrough

Walkthrough

Drop the libiconv parameter and Darwin build input, add Darwin RUSTFLAGS to dead-strip unused dylibs, and in Darwin postInstall rewrite any /nix/store/...-libiconv-... dylib install-names to /usr/lib/libiconv.2.dylib before running the existing otool -L validation.

Changes

Remove libiconv dependency from macOS build

Layer / File(s) Summary
Remove libiconv as build input
package.nix
The libiconv parameter is dropped from the exported Nix function arguments and removed from Darwin-specific buildInputs; Darwin RUSTFLAGS adds -Wl,-dead_strip_dylibs.
Rewrite detected Nix-store libiconv and run dylib validation
package.nix, nix/darwin-x64-package.nix
Darwin postInstall now scans ccusage with otool -L, rewrites any /nix/store/...-libiconv-... install-names to /usr/lib/libiconv.2.dylib, and continues the otool -L/grep -Ev validation that fails the build if non-system dylibs remain.

Sequence Diagram

sequenceDiagram
  participant ccusage as ccusage (binary)
  participant otool as otool -L
  participant installer as install_name_tool
  participant validator as otool-validator

  ccusage->>otool: list linked dylibs
  otool->>installer: identify /nix/store/...-libiconv-... targets
  installer->>ccusage: rewrite install-name -> /usr/lib/libiconv.2.dylib
  ccusage->>validator: final otool -L check for non-system dylibs
Loading

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • ccusage/ccusage#1289: Modifies the Darwin postInstall dylib handling at the same location, related to libiconv and otool checks.
  • ccusage/ccusage#1256: Hardened macOS otool -L-based dylib verification; related to the same validation gate and rewriting concerns.

Poem

🐰 I hopped through Nix with care and vim,
Twitched my nose — no libiconv in the mix,
Dead-strip flags trimmed bits that were dim,
otool hums, install_name_tool does its tricks,
A tidy build: carrot crumbs and code for kicks.

🚥 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 accurately summarizes the main change: removing an unused libiconv dependency from Darwin builds by adding a linker flag to strip unused dylibs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 fix/drop-unused-libiconv

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

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 11, 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 6da7e0c Commit Preview URL

Branch Preview URL
Jun 11 2026, 10:58 PM

@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.

No issues found across 1 file

Re-trigger cubic

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes — Removes unused libiconv from package.nix's Darwin buildInputs and the associated install_name_tool rewrite, fixing the startup crash on non-Nix macOS (#1251). The otool gate is preserved to catch any future stray /nix/store dylibs.

  • Drop libiconv from Darwin build inputs — removed from function args and buildInputs; nm -u confirms no iconv symbols exist, so the linker was recording an unused load-time dependency.
  • Remove the install_name_tool rewrite — no non-system dylibs remain, so the rewrite is dead code.
  • Update the otool comment — explains why libiconv is gone while keeping the gate itself.

Pullfrog  | View workflow run | Using Big Pickle (free via Pullfrog for OSS) | 𝕏

@pullfrog

pullfrog Bot commented Jun 11, 2026 •

Copy link
Copy Markdown
Contributor

Diagnosis: The build-mac-arm64 CI check failed because -Wl,-dead_strip_dylibs did not strip libiconv from the linked dylibs. The nixpkgs Darwin stdenv's cc-wrapper injects -liconv into the link flags; on this CI runner that flag arrives after dead_strip_dylibs has resolved, so the unused dylib remains in the binary. The postInstall safety gate then correctly rejects the non-system dylib.

Fix: Added a install_name_tool -change fallback in both package.nix and nix/darwin-x64-package.nix that rewrites any Nix store libiconv path to /usr/lib/libiconv.2.dylib before running the non-system dylib check. This matches the existing pattern used by apps/ccusage/scripts/stage-native-package.mjs and ensures the build succeeds whether or not -dead_strip_dylibs strips the dependency. The -dead_strip_dylibs flag is still kept — it works in local builds and is good practice.

Task list (5/5 completed)

Pullfrog  | View workflow run | via Pullfrog | Using Big Pickle (free via Pullfrog for OSS) | 𝕏

Removing libiconv from buildInputs was not enough: the nixpkgs Darwin stdenv
injects -liconv regardless, so the arm64 binary still recorded the unused
/nix/store libiconv dylib (the otool gate caught it). ccusage references no
iconv symbols, so add -Wl,-dead_strip_dylibs to the Darwin link, which drops
dylib load commands with no used symbols. The arm64 binary now links only
/usr/lib/libSystem.B.dylib (verified), so the install-name rewrite stays gone.

(Linux ships a fully static musl binary with no dynamic deps, so no equivalent
is needed there.)
@ryoppippi ryoppippi changed the title fix(nix): drop unused libiconv from the Darwin build fix(nix): strip the unused libiconv dependency on Darwin Jun 11, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jun 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

ccusage

npx https://pkg.pr.new/ccusage@1292

@ccusage/ccusage-darwin-arm64

npx https://pkg.pr.new/@ccusage/ccusage-darwin-arm64@1292

@ccusage/ccusage-darwin-x64

npx https://pkg.pr.new/@ccusage/ccusage-darwin-x64@1292

@ccusage/ccusage-linux-arm64

npx https://pkg.pr.new/@ccusage/ccusage-linux-arm64@1292

@ccusage/ccusage-linux-x64

npx https://pkg.pr.new/@ccusage/ccusage-linux-x64@1292

@ccusage/ccusage-win32-x64

npx https://pkg.pr.new/@ccusage/ccusage-win32-x64@1292

commit: 687df0c

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes — The prior 7afcec6 attempt to fix the Darwin crash by removing libiconv from buildInputs was incomplete: the nixpkgs Darwin stdenv still injects -liconv, leaving the unused dylib load command. The new commit adds -Wl,-dead_strip_dylibs to the Darwin link, which drops unreferenced dylib load commands — the proper root-cause fix.

  • Add -Wl,-dead_strip_dylibs to Darwin RUSTFLAGS — strips the unused libiconv dylib load command that the Darwin stdenv injects. The binary now links only /usr/lib/libSystem.B.dylib as verified by the author.
  • Reflow RUSTFLAGS to multi-line expression — adds a comment referencing #1251 explaining why the flag is needed.

Pullfrog  | View workflow run | Using Big Pickle (free via Pullfrog for OSS) | 𝕏

@ryoppippi
ryoppippi merged commit 7bcafc9 into main Jun 11, 2026
13 of 15 checks passed
@ryoppippi
ryoppippi deleted the fix/drop-unused-libiconv branch June 11, 2026 22:58
@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 687df0cde32d
Base SHA: b1c60c4467b1

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 b1c60c4467b1 778.9ms 762.5ms 62.1ms 3
PR pkg.pr.new 687df0c 964.0ms 825.0ms 67.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: b1c60c4467b1; PR package: 687df0c. 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 884.2ms 1.006s 0.88x 740.75 MiB 738.50 MiB 1.00x 1.14 GiB/s 1.00 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 199.6ms 192.1ms 1.04x 89.25 MiB 89.50 MiB 1.00x 5.04 GiB/s 5.24 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 978.9ms 1.03 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 878.0ms 1.15 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 177.1ms 5.69 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 131.9ms 7.64 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 41.5ms 39.0ms 1.06x 44.00 MiB 44.25 MiB 1.01x 0.04 MiB/s 0.04 MiB/s
claude session --offline --json 0.00 MiB 40.0ms 41.9ms 0.96x 44.00 MiB 44.00 MiB 1.00x 0.04 MiB/s 0.04 MiB/s
codex daily --offline --json 0.00 MiB 36.4ms 39.6ms 0.92x 44.25 MiB 44.25 MiB 1.00x 0.02 MiB/s 0.02 MiB/s
codex session --offline --json 0.00 MiB 43.3ms 38.9ms 1.11x 44.00 MiB 44.25 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 876.4ms 988.9ms 0.89x 731.00 MiB 740.00 MiB 1.01x 1.15 GiB/s 1.02 GiB/s
codex --offline --json 1.01 GiB 172.0ms 171.3ms 1.00x 91.00 MiB 91.75 MiB 1.01x 5.85 GiB/s 5.88 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 17.32 KiB 17.32 KiB +0.00 KiB 1.00x
installed native package binary 3324.84 KiB 3324.84 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: 687df0cde32d
Base SHA: b1c60c4467b1

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 b1c60c4467b1 952.0ms 987.7ms 63.4ms 3
PR pkg.pr.new 687df0c 819.8ms 1.104s 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: b1c60c4467b1; PR package: 687df0c. 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 950.2ms 949.2ms 1.00x 729.25 MiB 731.50 MiB 1.00x 1.06 GiB/s 1.06 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 207.8ms 205.3ms 1.01x 88.00 MiB 92.00 MiB 1.05x 4.84 GiB/s 4.90 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.000s 1.01 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 881.7ms 1.14 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 181.8ms 5.54 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 131.3ms 7.67 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 47.1ms 6.2ms 7.55x 43.00 MiB 3.00 MiB 0.07x 0.03 MiB/s 0.25 MiB/s
claude session --offline --json 0.00 MiB 45.9ms 6.6ms 6.91x 43.00 MiB 2.75 MiB 0.06x 0.03 MiB/s 0.23 MiB/s
codex daily --offline --json 0.00 MiB 43.7ms 5.7ms 7.69x 43.25 MiB 2.75 MiB 0.06x 0.02 MiB/s 0.15 MiB/s
codex session --offline --json 0.00 MiB 45.7ms 5.9ms 7.75x 43.25 MiB 2.75 MiB 0.06x 0.02 MiB/s 0.15 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 761.5ms 868.4ms 0.88x 730.00 MiB 726.25 MiB 0.99x 1.32 GiB/s 1.16 GiB/s
codex --offline --json 1.01 GiB 176.8ms 138.9ms 1.27x 92.75 MiB 89.00 MiB 0.96x 5.69 GiB/s 7.25 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 17.32 KiB 17.32 KiB +0.00 KiB 1.00x
installed native package binary 3324.84 KiB 3324.84 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: 6da7e0c20096
Base SHA: b1c60c4467b1

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 b1c60c4467b1 941.2ms 845.2ms 56.3ms 3
PR pkg.pr.new 6da7e0c 954.8ms 960.2ms 55.7ms 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: b1c60c4467b1; PR package: 6da7e0c. 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 763.5ms 772.6ms 0.99x 743.75 MiB 724.75 MiB 0.97x 1.32 GiB/s 1.30 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 176.2ms 175.5ms 1.00x 91.75 MiB 93.00 MiB 1.01x 5.71 GiB/s 5.74 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 837.3ms 1.20 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 793.7ms 1.27 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 159.9ms 6.29 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 122.5ms 8.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 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 46.6ms 36.5ms 1.28x 44.00 MiB 44.00 MiB 1.00x 0.03 MiB/s 0.04 MiB/s
claude session --offline --json 0.00 MiB 38.1ms 38.3ms 0.99x 44.25 MiB 44.25 MiB 1.00x 0.04 MiB/s 0.04 MiB/s
codex daily --offline --json 0.00 MiB 37.3ms 36.6ms 1.02x 44.25 MiB 44.25 MiB 1.00x 0.02 MiB/s 0.02 MiB/s
codex session --offline --json 0.00 MiB 36.9ms 36.0ms 1.02x 44.00 MiB 44.00 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 787.0ms 800.7ms 0.98x 737.50 MiB 733.75 MiB 0.99x 1.28 GiB/s 1.26 GiB/s
codex --offline --json 1.01 GiB 150.5ms 153.8ms 0.98x 91.25 MiB 90.75 MiB 0.99x 6.69 GiB/s 6.55 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 17.32 KiB 17.32 KiB +0.00 KiB 1.00x
installed native package binary 3324.84 KiB 3324.84 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: 6da7e0c20096
Base SHA: b1c60c4467b1

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 b1c60c4467b1 827.9ms 1.041s 62.2ms 3
PR pkg.pr.new 6da7e0c 772.1ms 1.135s 63.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: b1c60c4467b1; PR package: 6da7e0c. 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 857.3ms 999.3ms 0.86x 723.25 MiB 737.75 MiB 1.02x 1.17 GiB/s 1.01 GiB/s
bunx -p <pkg> ccusage codex --offline --json 1.01 GiB 203.9ms 207.9ms 0.98x 91.00 MiB 92.50 MiB 1.02x 4.94 GiB/s 4.84 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 977.3ms 1.03 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 950.3ms 1.06 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 176.2ms 5.71 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 138.0ms 7.29 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 60.6ms 7.6ms 7.96x 44.00 MiB 3.00 MiB 0.07x 0.03 MiB/s 0.20 MiB/s
claude session --offline --json 0.00 MiB 52.5ms 7.1ms 7.39x 44.00 MiB 2.75 MiB 0.06x 0.03 MiB/s 0.22 MiB/s
codex daily --offline --json 0.00 MiB 47.5ms 6.4ms 7.38x 44.25 MiB 2.75 MiB 0.06x 0.02 MiB/s 0.13 MiB/s
codex session --offline --json 0.00 MiB 48.2ms 6.3ms 7.71x 44.25 MiB 2.75 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 974.7ms 849.7ms 1.15x 725.00 MiB 724.75 MiB 1.00x 1.03 GiB/s 1.18 GiB/s
codex --offline --json 1.01 GiB 175.2ms 143.1ms 1.22x 93.50 MiB 91.00 MiB 0.97x 5.75 GiB/s 7.04 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 17.32 KiB 17.32 KiB +0.00 KiB 1.00x
installed native package binary 3324.84 KiB 3324.84 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.

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.

1 participant