Skip to content

Commit eb82dbf

Browse files
committed
Merge branch 'main' into fix/pricing-aliases
#1435 landed on main and solves replay deduplication the same problem this branch addressed, but from a better angle: it matches a forked session's leading events against the immutable parent stream instead of inferring the boundary from the child log alone. Resolution takes main's replay implementation wholesale and keeps only this branch's speed-tier work, which main does not have: - parser: main's CodexReplayState and replay_prefix plumbing, with recorded service tiers threaded back through the session entry visitor - aggregate: main's CodexAggregateRun and CodexReplayPlan wiring, with the recorded-tier dedupe records and bucket attribution kept - loader: main's replay plan threading, with tier reconciliation kept The branch's own replay work is dropped as superseded. That also retires the subagent turn-marker heuristic, its CLI-version gate, and the marker tests, which is a real improvement: the gate read session_meta cli_version, a field that resume rewrites from the original session, and a missing marker discarded a whole rollout. Matching against the parent stream depends on neither. main's replay tests are carried over intact. Codex totals are byte-identical to main on real rollouts, and recorded tiers still classify usage that config fallback would otherwise cover.
2 parents 8767143 + 409b4a5 commit eb82dbf

38 files changed

Lines changed: 3166 additions & 2580 deletions

‎.github/APPROVED_CONTRIBUTORS‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,5 @@ yukukotani pr
2525
zcutlip pr
2626

2727
tackeyy pr
28+
29+
hyfdev pr

‎.github/actions/setup-native-build-cache/action.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ runs:
99
steps:
1010
- name: Cache Cargo build
1111
if: inputs.platform == 'win32'
12-
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
12+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
1313
with:
1414
path: |
1515
~/.cargo/registry

‎.github/actions/setup-nix-cache/action.yaml‎

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ description: >
77
so the store never accumulates across runs the way a sticky disk did. Trimming
88
before save would only delete the unrooted crane deps and force a recompile, so
99
we let cache-nix-action save the whole non-substitutable set as-is and rely on
10-
purge to roll off stale entries.
10+
Blacksmith to evict stale entries.
1111
inputs:
1212
cache-scope:
1313
description: >
@@ -20,35 +20,30 @@ runs:
2020
using: composite
2121
steps:
2222
- name: Install Nix
23-
uses: nixbuild/nix-quick-install-action@2c9db80fb984ceb1bcaa77cdda3fdf8cfba92035 # v34
23+
uses: nixbuild/nix-quick-install-action@9f63be77f412a248c9d9a65a4c82cf066cdf8f0c # v35
2424
with:
2525
github_access_token: ${{ github.token }}
2626
nix_conf: |
2727
access-tokens = github.com=${{ github.token }}
2828
2929
# github.workflow is included so workflows that share a job id (e.g.
3030
# build-native-packages in both ci.yaml and release.yaml) do not collide on
31-
# the same primary-key / purge-prefix and evict each other's entries.
31+
# the same primary-key and block each other from saving updated entries.
3232
- name: Cache Nix store
3333
uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7.0.2
3434
with:
3535
# The commit sha is appended so every commit writes a fresh entry:
3636
# cache-nix-action skips saving on an exact primary-key hit, so a key that
3737
# only depended on the deps hash would persist the first run's store
3838
# forever. restore-prefixes-first-match (without the sha) then restores
39-
# the newest prior entry to warm the deps. The cache backend is
40-
# effectively unlimited, so one entry per commit is fine and `purge`
41-
# rolls off the old ones.
39+
# the newest prior entry to warm the deps. Blacksmith handles stale cache
40+
# eviction without the GitHub REST API used by this action's purge mode.
4241
# package.json only supplies the final binary version. It is excluded
4342
# because release bumps do not change the deps-only Crane derivation.
4443
# Likewise, checks and developer-tooling modules do not affect Cargo
4544
# artifacts; only package modules participate in this key.
4645
primary-key: nix-${{ github.workflow }}-${{ github.job }}-${{ inputs.cache-scope }}-${{ hashFiles('flake.lock', 'flake.nix', 'default.nix', 'package.nix', 'nix/packages.nix', 'nix/static-package.nix', 'nix/darwin-x64-package.nix', 'rust-toolchain.toml', 'rust/Cargo.lock', 'rust/**/Cargo.toml') }}-${{ github.sha }}
4746
restore-prefixes-first-match: nix-${{ github.workflow }}-${{ github.job }}-${{ inputs.cache-scope }}-${{ hashFiles('flake.lock', 'flake.nix', 'default.nix', 'package.nix', 'nix/packages.nix', 'nix/static-package.nix', 'nix/darwin-x64-package.nix', 'rust-toolchain.toml', 'rust/Cargo.lock', 'rust/**/Cargo.toml') }}-
48-
purge: true
49-
purge-prefixes: nix-${{ github.workflow }}-${{ github.job }}-${{ inputs.cache-scope }}-
50-
purge-created: 172800
51-
purge-primary-key: never
5247

5348
- name: Fix single-user Nix store ownership
5449
if: runner.os != 'Windows'

‎.github/scripts/generate-e2e-fixture.nu‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ def main [out_dir: string] {
44
if ($out_dir | is-empty) {
55
error make {msg: 'usage: generate-e2e-fixture.nu <out-dir>'}
66
}
7-
let fixture_dir = ([$out_dir, projects, preview-e2e-project, preview-e2e-session] | path join)
7+
let fixture_dir = [$out_dir, projects, preview-e2e-project, preview-e2e-session] | path join
88
mkdir $fixture_dir
99
let lines = (0..419 | each {|index|
10-
let day = (($index mod 28) + 1 | into string | fill --alignment right --character '0' --width 2)
10+
let day = ($index mod 28) + 1 | into string | fill --alignment right --character '0' --width 2
1111
[
1212
$"(claude_line $index $day 1 '10:00:00.000Z' 'claude-sonnet-4-20250514' 100 50 25 10 '0.12')"
1313
$"(claude_line $index $day 2 '12:30:00.000Z' 'claude-opus-4-20250514' 200 75 50 20 '0.34')"

‎.github/scripts/upsert-pr-comment.nu‎

Lines changed: 83 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -5,106 +5,112 @@ def main [] {
55
let pr_number = (required_env PR_NUMBER)
66
let marker = (required_env COMMENT_MARKER)
77
let body = (open --raw (required_env COMMENT_FILE))
8-
let comments = (gh_api_json [
8+
match (find_existing_comment $repository $pr_number $marker) {
9+
null => (create_comment $repository $pr_number $body)
10+
$existing => (update_or_recreate_comment $repository $pr_number $existing.id $body)
11+
}
12+
}
13+
def find_existing_comment [repository: string, pr_number: string, marker: string] {
14+
gh_api_json [
915
--paginate
1016
--slurp
1117
$"repos/($repository)/issues/($pr_number)/comments?per_page=100"
12-
] | flatten)
13-
let existing = (
14-
$comments | where {|comment|
15-
let login = if (($comment.user? | describe) =~ '^record') {
16-
$comment.user.login?
17-
} else {
18-
null
19-
}
20-
let comment_body = if (($comment.body? | describe) =~ '^string') {
21-
$comment.body
22-
} else {
23-
''
24-
}
25-
$login == 'github-actions[bot]' and ($comment_body | str contains $marker)
26-
} | sort-by created_at | reverse | get --optional 0
27-
)
28-
if $existing == null {
29-
create_comment $repository $pr_number $body
30-
} else {
31-
let update = (try_update_comment $repository $existing.id $body)
32-
if $update.status == 'ok' {
33-
return
34-
}
35-
if $update.status == 'missing' {
18+
]
19+
| flatten
20+
| where {|comment|
21+
let login = comment_login $comment
22+
let body = comment_body $comment
23+
$login == 'github-actions[bot]' and ($body | str contains $marker)
24+
}
25+
| sort-by created_at
26+
| reverse
27+
| get --optional 0
28+
}
29+
def comment_login [comment: record] {
30+
match ($comment | get --optional user) {
31+
{login: $login} => $login
32+
_ => null
33+
}
34+
}
35+
def comment_body [comment: record] {
36+
match ($comment | get --optional body) {
37+
$body if ($body | describe) == 'string' => $body
38+
_ => ''
39+
}
40+
}
41+
def update_or_recreate_comment [
42+
repository: string
43+
pr_number: string
44+
comment_id: int
45+
body: string
46+
] {
47+
let update = (try_update_comment $repository $comment_id $body)
48+
match $update.status {
49+
'ok' => null
50+
'missing' => {
3651
print --stderr 'Existing PR comment was missing; creating a new comment instead.'
3752
create_comment $repository $pr_number $body
38-
} else if $update.status == 'auth' {
39-
print --stderr $"Skipping PR comment because GitHub token cannot update comments: ($update.stderr | str trim)"
40-
} else {
41-
error make {
42-
msg: (format_gh_error ['update comment'] $update.result)
43-
}
4453
}
54+
'auth' => (
55+
print --stderr $"Skipping PR comment because GitHub token cannot update comments: ($update.stderr | str trim)"
56+
)
57+
_ => (error make {
58+
msg: (format_gh_error ['update comment'] $update.result)
59+
})
4560
}
4661
}
4762
def required_env [name: string] {
48-
let value = ($env | get --optional $name)
49-
if $value == null or ($value | is-empty) {
50-
error make {
51-
msg: $"($name) is required"
52-
}
63+
match ($env | get --optional $name) {
64+
null => (error make {msg: $"($name) is required"})
65+
$value if ($value | is-empty) => (error make {msg: $"($name) is required"})
66+
$value => $value
5367
}
54-
$value
5568
}
5669
def gh_api_json [args: list<string>] {
5770
let result = (gh_api_complete $args)
58-
if $result.exit_code != 0 {
59-
error make {
71+
match $result.exit_code {
72+
0 => ($result.stdout | from json)
73+
_ => (error make {
6074
msg: (format_gh_error $args $result)
61-
}
75+
})
6276
}
63-
$result.stdout | from json
6477
}
6578
def create_comment [repository: string, pr_number: string, body: string] {
66-
let result = (gh_api_with_body 'POST' $"repos/($repository)/issues/($pr_number)/comments" $body)
67-
if $result.exit_code != 0 {
68-
if (is_comment_write_auth_failure $result.stderr) {
79+
let result = (
80+
(gh_api_with_body
81+
'POST'
82+
$"repos/($repository)/issues/($pr_number)/comments"
83+
$body
84+
)
85+
)
86+
match $result.exit_code {
87+
0 => null
88+
_ if (is_comment_write_auth_failure $result.stderr) => (
6989
print --stderr $"Skipping PR comment because GitHub token cannot write comments: ($result.stderr | str trim)"
70-
} else {
71-
error make {
72-
msg: (format_gh_error ['create comment'] $result)
73-
}
74-
}
90+
)
91+
_ => (error make {
92+
msg: (format_gh_error ['create comment'] $result)
93+
})
7594
}
7695
}
7796
def try_update_comment [repository: string, comment_id: int, body: string] {
78-
let result = (gh_api_with_body 'PATCH' $"repos/($repository)/issues/comments/($comment_id)" $body)
79-
if $result.exit_code == 0 {
80-
return {
81-
status: 'ok'
82-
result: $result
83-
stderr: $result.stderr
84-
}
85-
}
86-
if ($result.stderr =~ 'HTTP 404') {
87-
return {
88-
status: 'missing'
89-
result: $result
90-
stderr: $result.stderr
91-
}
92-
}
93-
if (is_comment_write_auth_failure $result.stderr) {
94-
return {
95-
status: 'auth'
96-
result: $result
97-
stderr: $result.stderr
98-
}
99-
}
100-
{
101-
status: 'error'
102-
result: $result
103-
stderr: $result.stderr
104-
}
97+
let result = (
98+
(gh_api_with_body
99+
'PATCH'
100+
$"repos/($repository)/issues/comments/($comment_id)"
101+
$body
102+
)
103+
)
104+
let status = (match $result.exit_code {
105+
0 => 'ok'
106+
_ if $result.stderr =~ 'HTTP 404' => 'missing'
107+
_ if (is_comment_write_auth_failure $result.stderr) => 'auth'
108+
_ => 'error'
109+
})
110+
{status: $status, result: $result, stderr: $result.stderr}
105111
}
106112
def gh_api_with_body [method: string, endpoint: string, body: string] {
107-
let payload = (mktemp -t ccusage-pr-comment.XXXXXX | str trim)
113+
let payload = mktemp -t ccusage-pr-comment.XXXXXX | str trim
108114
{body: $body} | to json | save --force $payload
109115
let args = [
110116
--method

‎.github/workflows/approve-contributor.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
issues: write
1515
steps:
1616
- name: Checkout
17-
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
17+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
1818
with:
1919
ref: ${{ github.event.repository.default_branch }}
2020

‎.github/workflows/ci.yaml‎

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
outputs:
1515
code-changed: ${{ steps.detect.outputs.code-changed }}
1616
steps:
17-
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
17+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
1818
with:
1919
fetch-depth: 0
2020
persist-credentials: false
@@ -38,7 +38,7 @@ jobs:
3838
permissions:
3939
contents: read
4040
steps:
41-
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
41+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
4242
with:
4343
ref: ${{ github.event.pull_request.head.sha || github.sha }}
4444
persist-credentials: false
@@ -116,14 +116,14 @@ jobs:
116116
steps:
117117
# Checkout only places files for path mapping; no PR code is executed in
118118
# this privileged job.
119-
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
119+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
120120
with:
121121
ref: ${{ github.event.pull_request.head.sha || github.sha }}
122122
persist-credentials: false
123123
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
124124
with:
125125
name: tirith-sarif
126-
- uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2
126+
- uses: github/codeql-action/upload-sarif@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
127127
with:
128128
sarif_file: results.sarif
129129
category: tirith
@@ -144,7 +144,7 @@ jobs:
144144
permissions:
145145
contents: read
146146
steps:
147-
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
147+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
148148
with:
149149
ref: ${{ github.event.pull_request.head.sha || github.sha }}
150150
persist-credentials: false
@@ -162,7 +162,7 @@ jobs:
162162
pull-requests: read
163163

164164
steps:
165-
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
165+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
166166
with:
167167
ref: ${{ github.event.pull_request.head.sha || github.sha }}
168168
persist-credentials: false
@@ -222,7 +222,7 @@ jobs:
222222
binary: rust/target/release/ccusage.exe
223223

224224
steps:
225-
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
225+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
226226
- uses: ./.github/actions/build-linux-native-package
227227
if: matrix.platform == 'linux'
228228
with:
@@ -256,7 +256,7 @@ jobs:
256256
preview-url: ${{ steps.preview-url.outputs.url }}
257257

258258
steps:
259-
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
259+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
260260
- uses: ./.github/actions/setup-nix-cache
261261
# Packing only repackages the native binaries restored from the build
262262
# matrix below (no Rust build happens here), so the full `nix develop` dev
@@ -338,10 +338,10 @@ jobs:
338338
- pnpm
339339

340340
steps:
341-
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
341+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
342342
with:
343343
node-version: lts/*
344-
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
344+
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
345345
if: matrix.runner == 'pnpm'
346346
with:
347347
version: latest
@@ -452,7 +452,7 @@ jobs:
452452

453453
steps:
454454
- name: Checkout PR
455-
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
455+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
456456
with:
457457
ref: ${{ github.event.pull_request.head.sha }}
458458
persist-credentials: false
@@ -538,4 +538,4 @@ jobs:
538538
if: ${{ always() }}
539539
runs-on: ubuntu-slim
540540
steps:
541-
- uses: Kesin11/actions-timeline@44c9c178ffb2fb1d9859614a3ffa79ccfb77565e # v3.1.0
541+
- uses: Kesin11/actions-timeline@7bf79990b7c09f5dfb570ac30b814ca597bd538e # v3.1.1

0 commit comments

Comments
 (0)