Skip to content

fix(usage): score every rate-limit lane instead of only the codex lane - #578

Merged
andrei-hasna merged 2 commits into
mainfrom
fix/lane-aware-usage-health
Aug 12, 2026
Merged

fix(usage): score every rate-limit lane instead of only the codex lane#578
andrei-hasna merged 2 commits into
mainfrom
fix/lane-aware-usage-health

Conversation

@andrei-hasna

@andrei-hasna andrei-hasna commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

What was wrong

usage_health_for_snapshots picked one snapshot and discarded the rest:

.find(|snapshot| snapshot.limit_id.as_deref() == Some("codex"))
.or_else(|| snapshots.first())

Accounts now expose two lanes. When the generic codex lane hits 100%,
backend_blocked flips and the whole profile reports Exhausted — while the
codex_bengalfox (GPT-5.3-Codex-Spark) lane sits untouched. The function's doc
comment still asserted "Only the 5h and weekly Codex windows participate", which
was the stale assumption behind the selection.

Measured on station01 against installed 0.1.90, codewith usage --all --json:

targets total 28
authenticated 22
reported exhausted 22 of 22
remainingPercent exactly 0.0 on all 22
holding a lane under 100% 22 of 22
lanes present codex (22), codex_bengalfox (22)
codex lane used_percent 100.0 on all 22
codex_bengalfox used_percent 0.0 ×20, 3.0 ×1, 34.0 ×1

Every authenticated profile on the box was reported unusable while every one of
them had a free lane.

The trap this deliberately avoids

The one-line repair is to score all lanes and take the max. That is wrong — it
converts a false Exhausted into a false Healthy. If the codex lane really is
spent, a run on the default model still dies, and the selector would now route work
into it with confidence.

So the fix scores every lane and keeps the per-lane result:

  • Exhausted only when every scored lane is exhausted;
  • otherwise Healthy, carrying which lanes remain so a caller names a model
    rather than assuming any model will do;
  • the spent lanes stay in the payload, so "usable" can never be misread as
    "everything is available".

remaining_percent was a literal, not a measurement

Two sites hardcoded Some(0.0) on the exhausted arm — cli/src/usage_cmd.rs and
core/src/tools/handlers/auth_profile_usage_control.rs (the model-visible tool
handler; this second one was not in the original report). The classifier already
computed the real limiting figure and threw it away.

This matters beyond tidiness: a lane blocked by spend control or depleted credits
can still hold capacity. The existing spend-control test now asserts 80.0%
remaining on an exhausted profile — a figure the old code reported as 0.0.

AuthProfileUsageHealth::Exhausted gained a remaining_percent field, because
the variant previously had nowhere to carry one.

Tests

Two-sided, and the negative fixture is the one that catches the bad fix:

  • usage_health_reports_capacity_when_a_sibling_lane_is_freecodex at 100%,
    codex_bengalfox at 0%: must report usable and name the usable lane
    (codex_bengalfox / GPT-5.3-Codex-Spark), while still carrying the spent lane.
  • usage_health_stays_exhausted_when_every_lane_is_spent — every lane at 100%:
    must stay exhausted and offer no usable lane.

The first was observed failing against the pre-fix implementation
(got Exhausted { retry_at: Some(100) }) before any implementation change; the
second passed before and after, which is what makes it a guard rather than
decoration.

test auth_profile_usage::tests::usage_health_stays_exhausted_when_every_lane_is_spent ... ok
test auth_profile_usage::tests::usage_health_reports_capacity_when_a_sibling_lane_is_free ... ok
test result: ok. 32 passed; 0 failed; 0 ignored; 0 measured; 2282 filtered out

Scope — what this does not do

  • --profile any-healthy is not implemented. It appears in the rule corpus as a
    proposal; it does not exist in this codebase (0 occurrences). Nothing was invented
    for it here. The lane data this PR carries is the prerequisite for building it.
  • Auto-switch is not made model-aware. It uses the other classifier,
    core/src/usage_profile_health.rs, which carries the same single-lane selection via
    is_codex_limit (matching codex exactly, so codex_bengalfox is excluded), and it
    is fed a single snapshot with health keyed per profile. Making it select a
    (profile, model) pair is a genuine refactor across session, cooldown and selection
    state, not a hotfix. Left deliberately untouched and flagged rather than half-done.
  • The app-server and TUI brokers consume that second classifier, so they are unchanged
    by this PR.

Pre-existing failure, not from this change

agent::control::tests::spawn_agent_fork_last_n_turns_strips_parent_usage_hints
aborts with a debug-build stack overflow. It matches a usage test filter by name
only. Confirmed pre-existing by reverting the three changed files to
923fd0b0e and re-running that single test:

running 1 test
fatal runtime error: stack overflow, aborting
... (signal: 6, SIGABRT: process abort signal)

codex-rs/core/src/agent/ contains zero references to any symbol this PR touches.

Not verified

  • No release build was run; this is cargo test -p codex-core --lib plus a
    successful codex-cli compile. The published binary is unaffected until built.
  • The live codewith usage output was not re-measured against a rebuilt binary.
  • No profile on station01 currently has all lanes spent, so the all-exhausted case
    exists only as a fixture, never as a live observation.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

`usage_health_for_snapshots` selected a single snapshot — the `codex` lane, or
the first one — and discarded the rest. Accounts now expose two lanes, and when
the generic `codex` lane reaches 100% the whole profile was reported exhausted
while the `codex_bengalfox` (GPT-5.3-Codex-Spark) lane sat untouched. Measured on
station01: 22 of 22 authenticated profiles reported exhausted, every one of them
holding a lane under 100%.

Each lane is now scored independently and a profile is exhausted only when EVERY
lane is exhausted. The naive repair — scoring all lanes and taking the max — is
the mirror failure: it converts a false Exhausted into a false Healthy and routes
default-model work onto a genuinely spent lane. The per-lane result is therefore
carried, so callers can name which model still has capacity rather than assuming
any model will do.

`remaining_percent` on the Exhausted arm was the literal `Some(0.0)` in both the
CLI report and the model-visible tool handler. The classifier already computed
the real limiting figure and threw it away. A lane blocked by spend control or
depleted credits can still hold capacity — the regression test covers exactly
that case at 80% remaining — so the measured figure is now reported.

Two-sided fixtures: a profile with codex at 100% and codex_bengalfox at 0% must
report usable and name the usable lane; a profile with every lane at 100% must
stay exhausted and offer no lane.
@andrei-hasna

Copy link
Copy Markdown
Contributor Author

[REVIEW] NO_GO — #578 @ 555608d — lens: correctness+security+gates, reviewer unresolved-account011 (1 of 1)

Exact candidate read:

  • git log --oneline origin/main..HEAD — exit 0; 1 commit (555608d3b).
  • git diff origin/main...HEAD --stat — exit 0; 3 files, 324 insertions, 29 deletions.
  • Read the full diff and surrounding source in codex-rs/cli/src/usage_cmd.rs, codex-rs/core/src/auth_profile_usage.rs, and codex-rs/core/src/tools/handlers/auth_profile_usage_control.rs, plus the existing model-specific health and backend snapshot paths.

Setup and declared gates:

  • bun install — exit 0; setup only, 521 packages installed. This is not a test result.
  • package.json declares no test script, so this repository declares no test gate there.
  • package.json declares no typecheck script.
  • gh pr view 578 --repo hasna/codewith --json statusCheckRollup with safe field projection — exit 0; 28 checks read: 18 SUCCESS, 6 FAILURE, 3 SKIPPED, 1 IN_PROGRESS.

Forge checks read, by name and conclusion:

  • Bazel test on ubuntu-24.04 for x86_64-unknown-linux-gnu — FAILURE
  • cargo-deny — FAILURE
  • build-test — FAILURE
  • cla — SUCCESS
  • Check for spelling errors — SUCCESS
  • Blob size policy — SUCCESS
  • Build Codewith Linux CLI — SUCCESS
  • Detect changed areas — SUCCESS
  • python-sdk — SUCCESS
  • Bazel test on ubuntu-24.04 for x86_64-unknown-linux-musl — IN_PROGRESS
  • Format / etc — FAILURE
  • Bazel test on windows-latest for x86_64-pc-windows-gnullvm shard 1/4 — SUCCESS
  • sdks — SUCCESS
  • Bazel test on windows-latest for x86_64-pc-windows-gnullvm shard 2/4 — SUCCESS
  • Bazel test on windows-latest for x86_64-pc-windows-gnullvm shard 3/4 — SUCCESS
  • Bazel test on windows-latest for x86_64-pc-windows-gnullvm shard 4/4 — SUCCESS
  • Bazel test on windows-latest for x86_64-pc-windows-gnullvm (native main) — SKIPPED
  • cargo shear — SUCCESS
  • Argument comment lint package — SKIPPED
  • Bazel clippy on ubuntu-24.04 for x86_64-unknown-linux-gnu — SUCCESS
  • Bazel clippy on windows-latest for x86_64-pc-windows-gnullvm — SUCCESS
  • Argument comment lint - Linux — FAILURE
  • Verify release build on ubuntu-24.04 for x86_64-unknown-linux-gnu — SUCCESS
  • Verify release build on windows-latest for x86_64-pc-windows-gnullvm — SUCCESS
  • Argument comment lint - Windows — SUCCESS
  • CI results (required) — FAILURE
  • Bazel test on windows-latest for x86_64-pc-windows-gnullvm — SUCCESS
  • [code]smith — SKIPPED

Blocking P0/P1 findings:

  • P1 required-gate failure: build-test reports Formatting failed: Rust in the changed auth_profile_usage.rs; Format / etc is also failed.
  • P1 required-gate failure: Argument comment lint - Linux reports 7 errors in the new lane(...) test calls for anonymous limit_name, used_percent, and reached literal arguments; CI results (required) is failed.
  • P1 test regression visible from source: existing test usage_health_maps_rate_limit_snapshots still expects the old serialized health object and omits the newly mandatory usableLanes field. No package test gate exists to execute locally, and repository policy routes Rust tests through CI, but this assertion must be updated before the Rust test lane can pass.

Non-blocking follow-ups:

  • The failed GNU Bazel lane is an external V8 archive fetch returning HTTP 503; it is not caused by this diff, but the authoritative gate must be green before merge.
  • cargo-deny reports RUSTSEC-2026-0257 in pre-existing webbrowser 1.0.6; this PR does not modify dependency files. Track/fix it in the owning dependency lane; it is not a candidate-local blocker under this review scope, though the required check still prevents merge.
  • The model-visible get_usage path now treats a profile as healthy when any sibling lane has capacity, while its recommendation remains profile-only. Raw snapshots retain lane identity, so this is not a current P1, but a follow-up should make the recommended usable lane explicit or keep that recommendation scoped to the requested model lane.

Verdict: NO_GO. Fix the formatting, the seven argument-comment failures, and the stale serialization assertion; then rerun the affected format/lint/Rust-test forge lanes. Merge remains refused until the current-head authoritative checks are green.

@andrei-hasna

Copy link
Copy Markdown
Contributor Author

Focused remediation pushed at c722a49:

  • applied the Rust formatting required by build-test / Format / etc;
  • added the seven exact argument-name comments reported by Argument comment lint - Linux;
  • updated usage_health_maps_rate_limit_snapshots for the newly serialized usableLanes field.

Verification before push:

  • staged secrets scan — exit 0, 2 files scanned, 0 findings;
  • git diff --cached --check — exit 0;
  • lane-chosen cargo fmt -- --config imports_granularity=Item --check — exit 0 (this is not a declared package test gate).

just fmt ran the Rust formatter successfully but the aggregate command exited 1 because uv is not installed for the unrelated Python formatter lanes; this chosen local command is not being treated as a repository gate. The refreshed forge checks are the authoritative focused re-validation.

@andrei-hasna

Copy link
Copy Markdown
Contributor Author

[REVIEW] GO — #578 @ c722a49 — lens: correctness+security+gates, reviewer unresolved-account011 (1 of 1)

Focused re-review scope:

  • Re-read the exact two-commit candidate and the full three-file diff against the supplied fresh origin/main (c1589097ac9a309338ab9e77c9e88aead327115b), plus the surrounding health scoring, recommendation, CLI serialization, backend snapshot, and tool-handler source.
  • Re-checked only the prior named blockers and their direct regressions: Rust formatting, seven argument-comment violations, and the stale usableLanes serialization assertion.

Commands and measured results:

  • git log --oneline origin/main..HEAD — exit 0; 2 commits.
  • git diff origin/main...HEAD --stat — exit 0; 3 files, 332 insertions, 30 deletions.
  • bun install — exit 0; 521 packages installed. Setup only; this is not a test result. Its generated untracked bun.lock was removed, leaving the worktree clean.
  • gh pr view 578 --repo hasna/codewith --json statusCheckRollup with safe field projection — exit 0; 28 checks read: 24 SUCCESS, 1 FAILURE, 3 SKIPPED.
  • Lane's chosen non-gate: git merge-tree --write-tree origin/main HEAD — exit 0; current-main merge tree created without conflict. git diff origin/main <merge-tree> — exit 0; cmp against the reviewed PR patch — exit 0 (byte-identical).
  • Lane's chosen non-gate: git diff --check — exit 0. It was not used as a repository gate.
  • Redacted workspace scans of the captured PR diff, landing diff, and forge metadata — exit 0; 0 findings in each capture.

Declared repository gates:

  • package.json declares no test script. This repository declares no package test gate.
  • package.json declares no typecheck script.
  • No local Rust command was invented: repository policy routes the Rust build/test lanes through Blacksmith/GitHub Actions.

Forge checks read, by name and conclusion:

  • cargo-deny — FAILURE
  • Bazel test on ubuntu-24.04 for x86_64-unknown-linux-gnu — SUCCESS
  • cla — SUCCESS
  • Check for spelling errors — SUCCESS
  • Blob size policy — SUCCESS
  • build-test — SUCCESS
  • Build Codewith Linux CLI — SUCCESS
  • Detect changed areas — SUCCESS
  • python-sdk — SUCCESS
  • Bazel test on ubuntu-24.04 for x86_64-unknown-linux-musl — SUCCESS
  • Bazel test on windows-latest for x86_64-pc-windows-gnullvm shard 1/4 — SUCCESS
  • Format / etc — SUCCESS
  • sdks — SUCCESS
  • Bazel test on windows-latest for x86_64-pc-windows-gnullvm shard 2/4 — SUCCESS
  • Bazel test on windows-latest for x86_64-pc-windows-gnullvm shard 3/4 — SUCCESS
  • Bazel test on windows-latest for x86_64-pc-windows-gnullvm shard 4/4 — SUCCESS
  • Bazel test on windows-latest for x86_64-pc-windows-gnullvm (native main) — SKIPPED
  • cargo shear — SUCCESS
  • Argument comment lint package — SKIPPED
  • Bazel clippy on ubuntu-24.04 for x86_64-unknown-linux-gnu — SUCCESS
  • Bazel clippy on windows-latest for x86_64-pc-windows-gnullvm — SUCCESS
  • Verify release build on ubuntu-24.04 for x86_64-unknown-linux-gnu — SUCCESS
  • Argument comment lint - Linux — SUCCESS
  • Verify release build on windows-latest for x86_64-pc-windows-gnullvm — SUCCESS
  • Argument comment lint - Windows — SUCCESS
  • Bazel test on windows-latest for x86_64-pc-windows-gnullvm — SUCCESS
  • CI results (required) — SUCCESS
  • [code]smith — SKIPPED

Blocking P0/P1 findings: none.

The prior blockers are resolved: formatting is green, both argument-comment lanes are green, build-test is green, and the updated CLI serialization assertion is exercised by the green Rust test lanes. The current-main landing patch is exactly the patch reviewed.

Non-blocking follow-ups:

  • No new follow-up. The existing cargo-deny failure is the previously recorded pre-existing webbrowser 1.0.6 advisory; this PR changes no dependency files, and the required CI results (required) aggregate is green. The earlier profile-only/model-lane follow-up is unchanged and is not reopened by this focused re-review.

Verdict: GO.

@andrei-hasna
andrei-hasna merged commit 0f5990b into main Aug 12, 2026
37 of 40 checks passed
@andrei-hasna
andrei-hasna deleted the fix/lane-aware-usage-health branch August 12, 2026 20:38
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 12, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant