feat(codex): source session savings from the router - #1122
Conversation
The Codex status hook could show which model served a turn but not what it saved. Porting the Claude Code statusline's per-turn arithmetic is not an option here: Codex persists its own requested model on every turn and never the one that served, so pricing both sides of the comparison locally always yields zero. The router already computes the real number. Add GET /v1/sessions/:session_id/cost, authed by the rk_ key the Codex install already holds and scoped to the caller's installation, returning the session's committed actual/requested cost and their difference. It sums served turns plus billed auxiliary inference, so the total is what the session actually cost. The hook renders that value, fetching it in a detached subshell whose result the next turn reads, so no turn blocks on the network and every failure path degrades to model-only. A session where the router spent more shows no clause rather than a negative saving. Signed-off-by: munir-weave <munir@workweave.ai> (cherry picked from commit 2b53c64)
install.sh embeds the Codex status helper as a heredoc for the standalone curl | sh path, which has no sibling asset to copy. The savings lookup landed only in install/codex-status.sh, so every curl install kept shipping the old helper and could never render saved $X.XX. Regenerate the embed from the canonical helper and add the byte-identical check that install/tests/cc-statusline_test.sh already has for its own heredoc — nothing was keeping these two copies in sync, which is how they diverged in the first place. Signed-off-by: munir-weave <munir@workweave.ai> (cherry picked from commit 43348b1)
Apply workweave-bot's comment-length suggestions verbatim across the session-cost surface. Each keeps the non-obvious WHY (deliberate not-found indistinguishability, micros-at-encoding rounding, the installation_id authz boundary, SQLC's nullable-column pointer) while dropping the restated mechanics. Signed-off-by: munir-weave <munir@workweave.ai> (cherry picked from commit d40e3d2)
Apply workweave-bot's remaining comment-length suggestions verbatim. Signed-off-by: munir-weave <munir@workweave.ai> (cherry picked from commit 9334914)
0.2.11 predates the Codex status integration, so the published tarball ships no codex-status.sh and an install.sh that writes no Stop hook. Every Codex user is therefore still on the pre-hooks installer with no way to pick the feature up — the helper has no self-refresh, so a publish is the only delivery path. Signed-off-by: munir-weave <munir@workweave.ai>
|
Claude finished @munir-weave's task —— View job Posted one comment-length advisory on |
workweave-bot
left a comment
There was a problem hiding this comment.
Advisory only — comment-length nits. Won't block merge.
| // SessionCost is the committed router cost of one client session. | ||
| // Costs are USD micros ($1.00 = 1,000,000) summed as integers so no float rounding accumulates. | ||
| // Actual = router's chosen binding; Requested = client's originally-requested model. |
There was a problem hiding this comment.
| // SessionCost is the committed router cost of one client session. | |
| // Costs are USD micros ($1.00 = 1,000,000) summed as integers so no float rounding accumulates. | |
| // Actual = router's chosen binding; Requested = client's originally-requested model. | |
| // Costs are USD micros ($1.00 = 1,000,000) summed as integers so no float rounding accumulates. | |
| // Actual = router's chosen binding; Requested = client's originally-requested model. |
Was 3 lines; line 1 restates the type name. The WHY (micros-as-integer, Actual/Requested semantics) fits in 2.
What T-Rex did
|
| for candidate in "$helper_dir/config.toml" "$HOME/.codex/config.toml"; do | ||
| if [ -f "$candidate" ]; then | ||
| config="$candidate" | ||
| break | ||
| fi |
There was a problem hiding this comment.
Project helper uses user configuration
When a project-scoped helper remains after its adjacent config.toml has been removed, this loop selects ~/.codex/config.toml. The Stop hook then fetches the project session's cost using the user installation's router URL and key, so the terminal can show savings from the wrong installation. Only permit the home configuration for the known user-scoped helper; a project helper with no local managed configuration should skip the refresh.
Artifacts
Isolated Codex status scope reproduction script
- The authored executable harness creates missing and unreadable project-config cases and records the curl arguments, demonstrating which configuration scope the hook uses.
Codex status hook execution showing user credentials used when project config is missing
- A successful isolated hook execution records curl receiving the user-scoped URL and USER_SCOPE_SECRET for the missing project-config case, while the unreadable case makes no request; the fallback is real only for absence.
Repeated Codex status hook execution confirming scope behavior
- A second successful execution produces the same missing-config credential use and unreadable-config no-request result, confirming the observed shell behavior.
|
Reviewed this against root What I verified rather than assumed:
Two non-blocking notes, neither a rule violation:
Nothing to action on my side — this reads like it was written by someone who already had the conventions in hand. |
Re-lands the work from #1109, which merged into its stack parent (
munir/codex-persistent-status) instead ofmainafter #1098 landed first — so none of its commits were ever reachable frommain. Same four commits, cherry-picked ontomain, plus an npm version bump (see below).Why the endpoint exists
Codex writes its requested model into every transcript turn and never the model that served. Verified on a real routed session with a
/fm sonnetturn:turn_context.modelreadgpt-5.6-terraon all 27 turns andclaude-sonnetappeared zero times. So the Claude Code statusline's per-turn client-side pricing cannot be ported — it would price both sides of the comparison at the same model and reportsaved $0.00forever. Scraping the badge out of prose does not work either, sinceroutingMarkerFordeliberately emits nothing on sticky turns.GET /v1/sessions/:session_id/cost— authed by therk_key the Codex install already holds, scoped to the caller's installation. Returns the session's committed actual/requested cost in USD micros plus their difference, summingrouter.upstreamandrouter.auxiliary_inferenceso handover/compaction summaries the customer paid for are counted. Mounted on the passthrough group (product surface) so hosted/managed installs get it.Authorization is the
installation_idpredicate in the SQL, not a post-fetch check. Unknown, foreign, and not-yet-committed sessions all return the same 404 — distinguishing them would confirm a foreign session's existence.The hook renders what the router returns, fetching in a detached subshell whose result the next turn reads. No turn blocks on the network; every failure path degrades to model-only.
Why the version bump is in this PR
maincurrently sits at 0.2.11, which is already published. I pulled the published tarball to check: it contains nocodex-status.sh, and itsinstall.shhas zerohooks.Stopoccurrences. #1098 shipped the packaging but never bumped, so the entire Codex status feature — badge included — is unreachable by every user.codex-status.shhas no self-refresh (unlikecc-statusline.sh), so publishing is the only delivery path. Bumping to 0.2.12 here lets arouter-v0.2.12tag publish once this merges; the release workflow requires the tag be reachable frommain.Verified
npm pack --dry-runon the bumped package emitsworkweave-router-0.2.12.tgzcontainingcodex-status.sh(10.9kB) and aninstall.shthat writes the Stop hook.Honest edges
saved -$0.02is worse than silence.<$0.01, never$0.00— the latter is indistinguishable from "did not beat your selection".WEAVE_CODEX_STATUS_SAVINGS=0opts out.Verification
go build ./...,go vet,gofmt,make generate(no drift) — clean. Fullmake testgreen on the original PR; build + touched-package tests re-run after the cherry-pick.saved $0.32, plus negative / sub-cent / malformed-cache / unreachable-router / opt-out paths.ErrSessionCostNotFoundfor the same id. Seed data removed after.install.shheredoc sync fix Bugbot caught on feat(codex): source session savings from the router #1109 (the curl installer embeds its own copy and had gone stale), plus the byte-identical guard thatcc-statusline_test.shalready had andcodex-status_test.shlacked — which is how they diverged. Confirmed the guard fails on deliberate drift.🤖 Generated with Weave Router