Skip to content

ci: add tirith security scan and fail-fast preflight gate - #1383

Merged
ryoppippi merged 2 commits into
mainfrom
feature/tirith-ci-gate
Jun 29, 2026
Merged

ci: add tirith security scan and fail-fast preflight gate#1383
ryoppippi merged 2 commits into
mainfrom
feature/tirith-ci-gate

Conversation

@ryoppippi

@ryoppippi ryoppippi commented Jun 29, 2026

Copy link
Copy Markdown
Member

Summary

Adds a security gate to the CI workflow (tirith) plus a fail-fast preflight, so the expensive build/test work runs only after the cheap, high-signal security checks pass.

What changed

Two new jobs run unconditionally and in parallel at the front of ci.yaml (not gated on changes, so they also cover docs-only PRs and fork PRs, where local git hooks never run):

  • tirith-scan — scans the whole checkout with tirith, uploads SARIF to the Security tab, and gates downstream CI.
    • Wired on pull_request (not pull_request_target): fork PRs only get a read-only token and cannot leak secrets while their code is scanned. The SARIF upload is therefore skipped for forks (the scan + gate still run); pushes to main and same-repo PRs populate the Security tab.
    • tirith is installed from the flake's pinned nixpkgs so CI matches the dev shell.
    • --fail-on high blocks on high/critical findings. tirith's --ci exit code is 0 = clean, 1 = at/above threshold, 2 = findings below threshold; the gate treats 0/2 as pass and 1 (or any other code) as a block. The exit code is passed via env to avoid template injection.
    • *.snap is excluded: terminal snapshot test fixtures intentionally contain ANSI escapes that trip the ansi_escapes rule. The rule stays active for real source files.
  • preflight — runs the existing gitleaks and treefmt flake checks (treefmt carries actionlint + zizmor with the repo's exact ignore config) as an early fail-fast gate.

test and build-native-packages now needs both gates, so the heavy pipeline is skipped unless the gates pass. Heavy gate jobs run on blacksmith runners, consistent with the rest of the nix-based CI.

Why

  • Fail-fast: secret/malware/lint regressions stop the pipeline before any build/test compute.
  • Coverage gaps closed: the unconditional gates catch issues on docs-only PRs and fork PRs that previously bypassed the code-changed-gated checks and local hooks.

Testing

Validated locally against a CI-equivalent tree (git archive HEAD):

  • nix build .#checks.<sys>.gitleaks .#checks.<sys>.treefmt → pass (no leaks found, 0 changed).
  • actionlint on ci.yaml → clean (only the pre-existing parallel: extension ignored).
  • zizmor --offline --min-severity high --min-confidence high .github/workflows/ → no findings.
  • tirith on the clean tree with --fail-on high --exclude '*.snap' → exit 2 (pass, 0 high findings); a planted high/critical finding → blocks. The 8 remaining warnings (emoji variation selectors, instructional-comment config_injection) are below threshold and surface in the Security tab only.

Note: committing/pushing a workflow-only change trips a pre-existing single-file zizmor quirk in the git hooks (ci.yaml's parallel: extension is rejected as a lone input — "no inputs collected"). The whole-tree check CI runs passes, so this is a hook-invocation issue, not a content one.


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


Summary by cubic

Adds a CI security gate with tirith and a fast preflight so build/test only run after cheap, high-signal checks pass. Runs on all PRs (including docs-only and forks); SARIF publishing is isolated in a least-privilege job and uploads for same-repo PRs and pushes.

  • New Features
    • Added tirith-scan: repo-wide tirith --fail-on high; treats exit 0/2 as pass and 1 as block; excludes *.snap; installed from pinned nixpkgs; runs read-only and emits a SARIF artifact.
    • Added tirith-sarif-upload: separate job with security-events: write that never executes PR code; runs always and publishes SARIF (skipped for forks).
    • Added preflight: runs gitleaks and treefmt (actionlint + zizmor) as an early gate.
    • Gated test, build-native-packages, and action-timeline on the gates; action-timeline also needs tirith-sarif-upload.

Written for commit 8ff5cb8. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Security
    • Added an automated security scanning gate that fails CI for high-severity findings.
    • Security findings are summarized in CI and uploaded to the GitHub Security view when available (SARIF).
  • CI Improvements
    • Added a fast preflight stage for secret detection plus formatting/lint checks.
    • Updated downstream CI requirements so tests and builds wait for the new security and preflight gates to pass.

Add a security gate to the CI workflow so the expensive build/test work
runs only after the cheap, high-signal security checks pass.

Two new jobs run unconditionally and in parallel at the front of the
pipeline (not gated on `changes`, so they cover docs-only PRs and fork
PRs where local git hooks never run):

- `tirith-scan`: scans the whole checkout with tirith
  (https://tirith.sh/docs/ci/), uploads SARIF to the Security tab, and
  gates downstream CI. Wired on `pull_request` (not
  `pull_request_target`), so fork PRs only get a read-only token and
  cannot leak secrets while their code is scanned; the SARIF upload is
  therefore skipped for forks (the scan and gate still run). tirith is
  installed from the flake's pinned nixpkgs to match the dev shell.
- `preflight`: runs the existing `gitleaks` and `treefmt` flake checks
  (treefmt carries actionlint + zizmor with the repo's exact ignore
  config) as an early fail-fast gate before any build/test work.

`test` and `build-native-packages` now `needs` both gates, so they are
skipped unless the gates pass.

tirith details:
- `--fail-on high` blocks on high/critical findings. tirith's `--ci`
  exit code is 0 = clean, 1 = at/above threshold, 2 = findings below
  threshold; the gate treats 0/2 as pass and 1 (or any other code) as a
  block, and the scan exit code is passed via env to avoid template
  injection.
- `*.snap` is excluded because terminal snapshot test fixtures
  intentionally contain ANSI escapes that trip the `ansi_escapes` rule;
  the rule stays active for real source files.

Heavy gate jobs run on blacksmith runners, consistent with the rest of
the nix-based CI jobs.
@pullfrog

pullfrog Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

no API key found — this repo is configured to use deepseek/deepseek-v4-pro, which needs DEEPSEEK_API_KEY, but the runner has no key for it.

To fix: add the key as a GitHub Actions secret (referenced from your workflow's env: block) or as a Pullfrog secret in the console — or switch this repo to a different model (free models need no key).

Open repo secrets → · Configure model → · Setup docs → · Ask in Discord →

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | Rerun failed job ➔View workflow run | via Pullfrog | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 29, 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 8ff5cb8 Commit Preview URL

Branch Preview URL
Jun 29 2026, 01:38 PM

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4a0e8002-faf2-4a9f-92fc-c03e6ed0a945

📥 Commits

Reviewing files that changed from the base of the PR and between 5b09c71 and 8ff5cb8.

📒 Files selected for processing (1)
  • .github/workflows/ci.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/ci.yaml

📝 Walkthrough

Walkthrough

The CI workflow adds tirith-based security scanning, SARIF upload to the GitHub Security tab, and a preflight job for secret and formatting checks. Downstream test, build, and timeline jobs now wait for those new gates.

Changes

CI Security Gate

Layer / File(s) Summary
tirith-scan, SARIF upload, and preflight jobs
.github/workflows/ci.yaml
Adds tirith-scan with exit-code handling and SARIF artifact output, tirith-sarif-upload for Security tab upload, and preflight for gitleaks and treefmt checks.
Downstream dependency updates
.github/workflows/ci.yaml
Expands the needs lists for test, build-native-packages, and action-timeline to include the new security and preflight jobs.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hop through CI with a careful nose,
tirith scans and the green light glows.
Gitleaks listens, treefmt sings,
Now safer builds have bunny wings.

🚥 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 clearly and concisely describes the main CI change: a Tirith security scan plus a fail-fast preflight gate.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/tirith-ci-gate

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

❤️ Share

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

@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

@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: 1

🤖 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/ci.yaml:
- Around line 41-47: The CI gates are currently bootstrapping from the PR
checkout, which lets PR-controlled code influence `tirith-scan` and `preflight`.
Update the workflow to source trusted helper/actions and tool pins from the base
ref instead of `github.event.pull_request.head.sha`, while still scanning the PR
worktree as input data. Adjust the `actions/checkout`, `setup-nix-cache`, and
`Install tirith` steps accordingly, and ensure the `tirith-scan` / `preflight`
jobs use trusted flake checks and local actions from the base branch rather than
the PR-defined versions; if SARIF upload is needed, split it into a separate
upload job with `security-events: 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: b25c433e-3f2a-4b86-8ca4-3d933a50d19a

📥 Commits

Reviewing files that changed from the base of the PR and between 17911d6 and 5b09c71.

📒 Files selected for processing (1)
  • .github/workflows/ci.yaml

Comment thread .github/workflows/ci.yaml
@pkg-pr-new

pkg-pr-new Bot commented Jun 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

ccusage

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

@ccusage/ccusage-darwin-arm64

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

@ccusage/ccusage-darwin-x64

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

@ccusage/ccusage-linux-arm64

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

@ccusage/ccusage-linux-x64

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

@ccusage/ccusage-win32-x64

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

commit: 8ff5cb8

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

2 issues found across 1 file

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/ci.yaml">

<violation number="1" location=".github/workflows/ci.yaml:47">
P1: Security scanner is selected from the untrusted PR checkout. Pin tirith from a trusted/base ref so fork PRs cannot replace or neuter the gate that is supposed to scan them.</violation>

<violation number="2" location=".github/workflows/ci.yaml:120">
P1: Preflight gate trusts the PR to define the checks it is enforcing. Run the gate implementation from a trusted/base revision or otherwise prevent PR changes from disabling gitleaks/treefmt.</violation>
</file>

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

Re-trigger cubic

Comment thread .github/workflows/ci.yaml
persist-credentials: false
- uses: ./.github/actions/setup-nix-cache
- name: Secret scan and workflow lint
run: nix build --print-build-logs .#checks.x86_64-linux.gitleaks .#checks.x86_64-linux.treefmt

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: Preflight gate trusts the PR to define the checks it is enforcing. Run the gate implementation from a trusted/base revision or otherwise prevent PR changes from disabling gitleaks/treefmt.

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

<comment>Preflight gate trusts the PR to define the checks it is enforcing. Run the gate implementation from a trusted/base revision or otherwise prevent PR changes from disabling gitleaks/treefmt.</comment>

<file context>
@@ -21,8 +21,106 @@ jobs:
+          persist-credentials: false
+      - uses: ./.github/actions/setup-nix-cache
+      - name: Secret scan and workflow lint
+        run: nix build --print-build-logs .#checks.x86_64-linux.gitleaks .#checks.x86_64-linux.treefmt
+
   test:
</file context>

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.

Same pull_request (not pull_request_target) threat model: the PR already owns the workflow definition, so running gitleaks/treefmt from a base ref can't stop a fork from disabling them, and fork PRs are read-only with no secrets. The suggested change would also break the intentional flake-check reuse, since nix build .#checks… runs against the flake's own source rather than an external worktree.

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.

Same pull_request threat model as the tirith thread: the PR owns the workflow definition, so running gitleaks/treefmt from a base ref can't stop a fork from disabling them (it would just remove the step / needs: edge), and fork tokens are read-only with no secrets. It would also break the intentional flake-check reuse, since nix build .#checks.*.{gitleaks,treefmt} runs against the flake's own source rather than an external worktree. preflight holds contents: read only, so there is no privilege to protect here.

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.

The threat-model point is fair, but the parent comment is too broad for this PR: on pull_request, the PR already owns ci.yaml, fork tokens are read-only/no-secrets, and base-pinning would break the intended flake-check reuse. So the base-ref hardening doesn’t apply here.

Comment thread .github/workflows/ci.yaml
persist-credentials: false
- uses: ./.github/actions/setup-nix-cache
- name: Install tirith
run: nix profile install --inputs-from . nixpkgs#tirith

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: Security scanner is selected from the untrusted PR checkout. Pin tirith from a trusted/base ref so fork PRs cannot replace or neuter the gate that is supposed to scan them.

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

<comment>Security scanner is selected from the untrusted PR checkout. Pin tirith from a trusted/base ref so fork PRs cannot replace or neuter the gate that is supposed to scan them.</comment>

<file context>
@@ -21,8 +21,106 @@ jobs:
+          persist-credentials: false
+      - uses: ./.github/actions/setup-nix-cache
+      - name: Install tirith
+        run: nix profile install --inputs-from . nixpkgs#tirith
+      # One pass produces the SARIF report and sets the gate exit code. tirith's
+      # `--ci` exit code is: 0 = no findings, 1 = findings at/above --fail-on,
</file context>

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.

Premise is right that tirith resolves through the PR's flake, but pinning it from base can't prevent neutering: on pull_request GitHub runs the PR's own ci.yaml, so a fork would just disable the step or needs: edge rather than swap the binary, and forks have a read-only token with no secrets to protect. Real tamper-resistance needs a base-defined workflow (pull_request_target/workflow_run), the secret-exposing model this PR intentionally avoids; it would also break the deliberate dev-shell-consistent install.

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.

Same reasoning as the CodeRabbit thread above. On pull_request the PR runs its own ci.yaml, so pinning tirith from a base ref can't prevent neutering — a fork would just drop the step or the needs: edge, not swap the binary. Fork tokens are read-only with no secrets, and base-pinning would break the deliberate dev-shell-consistent install (--inputs-from .). As defense-in-depth I isolated the only write scope (security-events: write, SARIF upload) into a job that executes no PR code (commit 8ff5cb8); tirith-scan is now contents: read only.

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.

The parent comment was wrong here: on pull_request, the PR owns the workflow, so base-pinning tirith wouldn’t stop a fork from dropping the step or the needs: edge. The meaningful hardening is the least-privilege split you added in tirith-sarif-upload, which keeps security-events: write out of the PR-code job.

@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 5b09c7130299
Base SHA: 17911d62fe24

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

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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 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 281.7ms 3.57 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 272.5ms 3.69 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 115.2ms 8.74 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 86.8ms 11.59 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 native ccusage binary 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 33.3ms 6.4ms 5.24x 53.50 MiB 10.20 MiB 0.19x 0.05 MiB/s 0.24 MiB/s
claude session --offline --json 0.00 MiB 38.0ms 3.7ms 10.31x 53.75 MiB 10.21 MiB 0.19x 0.04 MiB/s 0.42 MiB/s
codex daily --offline --json 0.00 MiB 33.9ms 3.2ms 10.60x 53.75 MiB 8.19 MiB 0.15x 0.03 MiB/s 0.27 MiB/s
codex session --offline --json 0.00 MiB 31.4ms 3.0ms 10.53x 53.75 MiB 8.19 MiB 0.15x 0.03 MiB/s 0.29 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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published native ccusage binary 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 334.7ms 275.8ms 1.21x 940.33 MiB 960.34 MiB 1.02x 3.01 GiB/s 3.65 GiB/s
codex --offline --json 1.01 GiB 117.5ms 89.5ms 1.31x 399.29 MiB 419.29 MiB 1.05x 8.57 GiB/s 11.25 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 18.25 KiB 18.25 KiB +0.00 KiB 1.00x
installed native package binary 4026.25 KiB 4026.25 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: 5b09c7130299
Base SHA: 17911d62fe24

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

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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 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 285.2ms 3.53 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 256.2ms 3.93 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 120.7ms 8.34 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 93.8ms 10.73 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 38.5ms 37.1ms 1.04x 54.00 MiB 53.75 MiB 1.00x 0.04 MiB/s 0.04 MiB/s
claude session --offline --json 0.00 MiB 35.8ms 31.9ms 1.12x 53.75 MiB 54.00 MiB 1.00x 0.04 MiB/s 0.05 MiB/s
codex daily --offline --json 0.00 MiB 29.0ms 30.8ms 0.94x 53.75 MiB 53.75 MiB 1.00x 0.03 MiB/s 0.03 MiB/s
codex session --offline --json 0.00 MiB 27.9ms 33.5ms 0.83x 53.75 MiB 53.75 MiB 1.00x 0.03 MiB/s 0.03 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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 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 336.5ms 281.5ms 1.20x 944.34 MiB 958.34 MiB 1.01x 2.99 GiB/s 3.58 GiB/s
codex --offline --json 1.01 GiB 118.7ms 122.1ms 0.97x 417.29 MiB 421.29 MiB 1.01x 8.48 GiB/s 8.25 GiB/s

Artifact size

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

Address CodeRabbit's security review: keep the elevated
`security-events: write` scope out of any job that executes PR-controlled
code.

- `tirith-scan` now holds only `contents: read`. It still runs the local
  action, the PR flake and the installed tirith (it must, to scan the PR),
  but with no write scope a neutered scan cannot abuse any privilege —
  the same trust model as the existing `test`/`build` jobs that run the
  PR's `nix flake check`.
- The SARIF is handed off as an artifact and published by a new
  `tirith-sarif-upload` job that never executes PR code: it only checks
  out files for path mapping and runs trusted, pinned actions
  (download-artifact + codeql upload-sarif). This is the only job with
  `security-events: write`.
- The upload runs `always()` so findings reach the Security tab even when
  the gate blocks the PR, and is skipped for fork PRs (read-only token
  cannot upload).
@github-actions

Copy link
Copy Markdown
Contributor

ccusage performance comparison

PR SHA: 8ff5cb84e1c2
Base SHA: 17911d62fe24

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

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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 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 268.8ms 3.75 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 303.2ms 3.32 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 115.5ms 8.72 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 87.5ms 11.51 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 37.1ms 37.1ms 1.00x 53.25 MiB 54.00 MiB 1.01x 0.04 MiB/s 0.04 MiB/s
claude session --offline --json 0.00 MiB 33.3ms 31.8ms 1.05x 53.75 MiB 53.50 MiB 1.00x 0.05 MiB/s 0.05 MiB/s
codex daily --offline --json 0.00 MiB 27.8ms 29.5ms 0.94x 53.75 MiB 53.50 MiB 1.00x 0.03 MiB/s 0.03 MiB/s
codex session --offline --json 0.00 MiB 31.0ms 27.8ms 1.11x 53.75 MiB 53.75 MiB 1.00x 0.03 MiB/s 0.03 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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 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 329.5ms 294.6ms 1.12x 964.34 MiB 946.34 MiB 0.98x 3.06 GiB/s 3.42 GiB/s
codex --offline --json 1.01 GiB 114.9ms 120.3ms 0.95x 397.29 MiB 421.30 MiB 1.06x 8.77 GiB/s 8.37 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 18.25 KiB 18.25 KiB -0.00 KiB 1.00x
installed native package binary 4026.25 KiB 4026.25 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: 8ff5cb84e1c2
Base SHA: 17911d62fe24

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

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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 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 244.7ms 4.11 GiB/s 1
claude --offline --json Installed native binary 1.01 GiB 242.8ms 4.15 GiB/s 1
codex --offline --json Package wrapper 1.01 GiB 113.3ms 8.89 GiB/s 1
codex --offline --json Installed native binary 1.01 GiB 86.1ms 11.69 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 native ccusage binary 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 39.7ms 3.1ms 12.62x 53.75 MiB 10.21 MiB 0.19x 0.04 MiB/s 0.49 MiB/s
claude session --offline --json 0.00 MiB 31.7ms 3.6ms 8.83x 53.75 MiB 10.20 MiB 0.19x 0.05 MiB/s 0.43 MiB/s
codex daily --offline --json 0.00 MiB 29.3ms 2.9ms 10.05x 53.75 MiB 8.19 MiB 0.15x 0.03 MiB/s 0.29 MiB/s
codex session --offline --json 0.00 MiB 31.1ms 2.8ms 11.18x 53.75 MiB 8.20 MiB 0.15x 0.03 MiB/s 0.31 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, 2597 files), Codex /home/runner/_work/_temp/ccusage-large-codex-fixture (1.01 GiB, 2597 files)
Base runs the published ccusage package from pkg.pr.new, installed before measurement; PR runs the published native ccusage binary 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 325.0ms 269.4ms 1.21x 954.33 MiB 948.34 MiB 0.99x 3.10 GiB/s 3.74 GiB/s
codex --offline --json 1.01 GiB 112.8ms 89.8ms 1.26x 417.29 MiB 413.29 MiB 0.99x 8.92 GiB/s 11.21 GiB/s

Artifact size

Artifact Base PR Delta Ratio
packed ccusage-*.tgz 18.25 KiB 18.25 KiB -0.00 KiB 1.00x
installed native package binary 4026.25 KiB 4026.25 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 cdda182 into main Jun 29, 2026
36 checks passed
@ryoppippi
ryoppippi deleted the feature/tirith-ci-gate branch June 29, 2026 22:36
@github-actions github-actions Bot mentioned this pull request Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants