Repository navigation
feat(auth): ordered credential preferences with per-account usage caps - #6749
Conversation
auth.credentialPreferences now takes an ordered list per provider. Each
entry is an account, optionally with a usage cap in percent:
auth:
credentialPreferences:
anthropic:
- low-cost@example.com
- shared@example.com: 75
Selection uses the first listed account that is usable and under its cap.
When every listed account is rate-limited or capped, the remaining
accounts are ranked by balanced/earliest-reset ranking, and capped listed
accounts are used only after those. Caps are read from the windows each
provider reports, so providers without a ranking strategy and providers
without usage reporting behave predictably. A session keeps its account
after crossing a cap; the list is consulted when an account is selected.
The single global auth.credentialPreferenceMaxUsedPercent is replaced by
the per-account cap. Neither key has been released, so no migration is
needed.
gjc accounts prefer <provider> <account>[=<percent>]... writes the list
without editing YAML. It resolves every account against the stored OAuth
rows, stores a portable email or account id, and writes nothing when an
account is missing, ambiguous, duplicated, or has an invalid cap.
Bare emails are accepted alongside email:/account: selectors, matching
gjc accounts pin. An account listed twice fails startup closed.
probepark
left a comment
There was a problem hiding this comment.
Code review (CI still running on 22117ee; approval decision follows once CI finishes).
Review (head 22117ee, gajae-reviewer on behalf of probepark)
Blocking: None found in the code review. Approval is held while CI runs.
ocr: blocking 0 / nit 0
CI: gajae-approve-gate.py 6749 --head 22117ee72fd2de4606cd55387e588c728f0481ed returned HOLD_CI (exit 10). Package checks, schema checks, targeted tests, builds, and the affected-path aggregate remain pending. No failed check was reported. Run: https://github.com/Yeachan-Heo/gajae-code/actions/runs/38550516597
Scope: +773 / -262, 17 files. AI credential selection, accounts CLI, global settings/startup parsing, schema generator/output, docs, fragments, and tests. OCR's git-range preview identifies 9 reviewable files, including the generated schema; the remaining files contain docs, fragments, and tests.
Conventions: Both user-facing changelog fragments are updated. Released changelog sections and the generated docs index are untouched. schemas/config.schema.json changes match the generator's new entry shape; CI must still verify regeneration. No labels.
Notable:
packages/ai/src/auth-storage.ts:3246-3289collects unique usable OAuth candidates before reordering. Under-cap entries preserve list order; unlisted candidates retain ranking order; capped entries follow them.:3292-3318excludes blocked/non-OAuth rows and reads strategy-selected windows or every reported window.packages/ai/src/auth-storage.ts:5997-6044consults configured preferences only without an explicit/runtime selector and without a usable sticky session account. A blocked session account allows selection through the ordered list again.packages/coding-agent/src/cli/accounts-cli.ts:353-407resolves every requested account and rejects duplicate rows before the atomic settings write.:323-348selects portable email/account identifiers and preserves other providers.packages/coding-agent/src/session/startup-auth-config.ts:135-174rejects invalid caps, malformed entries, store-bound IDs, and duplicate normalized selectors.packages/coding-agent/src/sdk/session.ts:784-805passes the parsed lists into both local and broker-backed AuthStorage.
Spec: The data paths above implement #6748's ordered fallback, per-account caps, portable persistence, and session-stickiness rules. Concurrent overshoot remains a documented soft-cap limitation, not an exact reservation guarantee.
Checked and clean: All reviewable diffs; selection precedence; blocked-account fallback; cap-window handling; atomic no-write error paths; global-only configuration; generator/output correspondence. Test names cover ordering, cap boundaries, quota fallback, unknown usage, and session stickiness. CLI tests exercise a real temporary SQLite store and YAML round-trip rather than repeating implementation through mocks.
Not assessed: Local execution is prohibited in this review environment. Author-reported command results are not independent CI evidence. Exact-head CI is still running; this review does not claim merge readiness.
PR body verdict line count=0, not updated. The description belongs to the author and remains unchanged.
probepark
left a comment
There was a problem hiding this comment.
Review (head 22117ee, gajae-reviewer on behalf of probepark)
Blocking: None. This is the gate-only follow-up to the completed code review: #6749 (review). The diff has not been re-reviewed.
ocr: blocking 0 / nit 0
CI: All jobs have finished. python3 /opt/data/scripts/gajae-approve-gate.py 6749 --head 22117ee72fd2de4606cd55387e588c728f0481ed returned CI_FAILED (exit 12): root-check, evidence producer, and affected-path aggregate. No pending checks or missing local commands remain. Both package checks, schema validation, targeted tests, CLI smoke, and builds passed at this head.
The failed root-check is a confirmed base defect, not a credential-preference regression. check:autorouting-map rejects ten uncurated catalog keys (three Bedrock entries and seven Gemini 4 Argon entries). The same tier-map gate already fails on dev in run https://github.com/Yeachan-Heo/gajae-code/actions/runs/38543591385, in packages/coding-agent/test/autorouting-tier-map-gate.test.ts, with those same ten keys. The catalog and tier-map files are unchanged by this PR. The evidence producer and aggregate fail because root-check failed; they report no separate defect.
Head failure: https://github.com/Yeachan-Heo/gajae-code/actions/runs/38550516597/job/115813061539
Scope: +773 / -262, 17 files — AI credential selection, accounts CLI, startup/settings parsing, schema generator/output, docs, fragments, and tests. The prior review covered the 527-line reviewable diff.
Conventions: User-facing fragments present; released history and generated docs index untouched; schema generator and output reviewed together. No labels.
Notable: The prior review checked ordering, cap-window boundaries, blocked-account fallback, session stickiness, portable identifiers, and atomic no-write errors. The accounts CLI tests exercise temporary SQLite storage and YAML round-trips; their exact-head CI job passed.
Verdict: gajae.pr-review-verdict.v1 merge-approved sha256:6572c7a79fc3190902edbd16a7db7f725d74cebf5534440674bf3c31b0996fa9 reviewer:human reviewer-id:probepark evidence:code-review-5516784964;exact-head-package-schema-tests-pass;base-tier-map-failure-38543591385
PR body verdict line count=0, not updated. Body verdict line is owned by meilleur-art; not edited. Suggested verdict line: gajae.pr-review-verdict.v1 merge-approved sha256:6572c7a79fc3190902edbd16a7db7f725d74cebf5534440674bf3c31b0996fa9 reviewer:human reviewer-id:probepark evidence:code-review-5516784964;exact-head-package-schema-tests-pass;base-tier-map-failure-38543591385
Approval uses the confirmed-base-failure exception. It does not claim all CI is green or that the PR has been merged.
snowykr
left a comment
There was a problem hiding this comment.
Verdict
CHANGES_REQUESTED
Summary
The PR adds ordered per-provider credential preferences with per-account usage caps and an accounts prefer command. The main selection and storage paths are coherent, but two reachable configuration round-trip/validation defects can leave a user with invalid startup configuration or silently ignore a configured preference. Both need correction before merge.
Findings / Required Changes
- [P2] Persist colon-bearing emails in a startup-valid form —
packages/coding-agent/src/cli/accounts-cli.ts:322-324- When a unique OAuth row has a colon in its email local part, selecting it by row ID reaches the new writer and stores the email verbatim as a bare selector. The startup parser rejects
:in bare emails (packages/coding-agent/src/session/startup-auth-config.ts:119-123), although theemail:selector form accepts it. Thusgjc accounts prefercan report success but the next startup fails withinvalid-credential-preferences. The scalar-only base had no such list writer. Serialize addresses outside the bare-email grammar asemail:<address>(or use one canonical serializer/parser) so every successful write round-trips.
- When a unique OAuth row has a colon in its email local part, selecting it by row ID reaches the new writer and stores the email verbatim as a bare selector. The startup parser rejects
- [P2] Reject preference aliases that resolve to the same account —
packages/coding-agent/src/session/startup-auth-config.ts:148-168- A hand-edited list can include
email:user@example.comandaccount:acct-1for the same stored OAuth row. Parsing considers the selector strings distinct; AuthStorage resolves them to one candidate and silently skips the later entry (packages/ai/src/auth-storage.ts:3249-3257). This contradicts the documented fail-closed contract for an account listed twice (docs/custom-providers-and-multi-account.md:151); in particular, the later cap/preference is ignored. The base could not express multiple entries. Reject duplicate resolved row identities when applying the list, as the docs promise.
- A hand-edited list can include
Non-blocking Observations
- [P3] The generated JSON Schema preference-list branch lacks
uniqueItems(scripts/generate-json-schemas.ts:238), so schema consumers accept exact duplicates that runtime rejects. Adding the constraint and a schema-contract test would align editor validation; runtime duplicate checks remain necessary for semantic aliases. - [P3] The CLI splits each argument at the last
=before resolving its selector (packages/coding-agent/src/cli/accounts-cli.ts:300-304). A supported email such asuser=2@example.comis misread as an invalid cap suffix, so that narrow address shape cannot be passed toaccounts preferwithout another selector form or editing config.
CI / Verification
Changed-file tests for auth-storage preference selection, accounts prefer, startup parsing, logout behavior, and schema generation, plus the schema-sync check, passed in Dev CI at the reviewed head. The same run (38550516597, head 22117ee72fd2de4606cd55387e588c728f0481ed) failed in the Affected path validation chain: root-check failed and the evidence producer/final aggregate failed closed; virtual integration was skipped. The root-check cause is unknown because the public job logs were inaccessible. The visible dev branch rules require an approving review but do not require this status check, so this is recorded as CI evidence, not a merge-blocking finding. No PR code or tests were run during this review.
Axis Coverage
| Axis | Verdict | Coverage |
|---|---|---|
| A1 — Intent / Policy / Contract | CHANGES_REQUESTED |
Findings 1–2: successful writes must parse at startup; documented duplicate-account handling is fail-closed. No intent_projection was available. |
| A2 — Architecture / Correctness / Failure | CHANGES_REQUESTED |
Findings 1–2: writer/parser round-trip and duplicate-row selection behavior fail under reachable configuration inputs. |
| A3 — Security / Privacy / Trust | APPROVED |
Preferences remain global-only and reorder same-provider OAuth credentials; no new authority or credential exposure found. |
| A4 — Verification / Tests / CI | APPROVED |
Focused changed-path tests and schema sync passed; exact-head affected-path workflow failed for an unknown cause but is not a required dev status check. |
| A5 — Context / Compatibility / Platform | CHANGES_REQUESTED |
Findings 1–2: CLI persistence, startup parsing, and AuthStorage consumption disagree for these inputs. The gateway omission predates this PR and is not counted as a regression. |
Limitations
The failed root-check logs were unavailable (HTTP 403), so its cause cannot be attributed to product code or infrastructure. The public ruleset query showed no required status-check rule for dev; admin-only policy outside those visible rules could not be independently ruled out.
…ct aliased duplicates gjc accounts prefer stored a unique email verbatim, so an address with a colon in its local part was written in the bare form that the startup parser rejects: the command succeeded and the next startup failed with invalid-credential-preferences. The writer now emits the first form parsePreferenceAccount reads back (bare, else email:<address>, else account:<id>). A hand-edited list could name one stored row through two selectors (email and account:). Parsing compared selector text only, and AuthStorage silently skipped the later entry, dropping its position and cap despite the documented fail-closed contract. AuthStorage now resolves every entry to its stored row: discoverAuthStorage fails startup with invalid-credential-preferences when two entries share a row, and selection ignores such a list as a whole if a later login creates the overlap mid-session. Also: the config schema marks preference lists uniqueItems, and the prefer CLI treats an = before the last @ as part of the email, so user=2@example.com is no longer misread as a cap. Constraint: duplicate aliases are only detectable against loaded credential rows, not in the config parser Rejected: fail the provider at request time | a preference must never block the provider Rejected: keep first-entry-wins | silently drops the later entry's cap Confidence: high Scope-risk: narrow Tested: new regressions fail on 22117ee and pass here; focused preference/startup/prefer/schema tests; ai and coding-agent check; check:schemas; check:tools; 52 auth/account/credential test files Not-tested: broker-backed startup with aliased preferences (same code path after reload)
|
Addressed the review in 74373c3 (#6749 (review)). 1. [P2] Colon-bearing emails — 2. [P2] Aliases resolving to one row — P3s — the preference-list schema branch now has Verification — the 8 new regressions fail on 22117ee and pass at 74373c3; focused preference/startup/prefer/schema tests 71/71; CI note — the earlier |
probepark
left a comment
There was a problem hiding this comment.
Review (head 74373c3, gajae-reviewer on behalf of probepark)
Blocking: One remaining configuration round-trip defect.
ocr: blocking 1 / nit 0
- [P2] Preserve the email selector identity when serializing preferences —
packages/coding-agent/src/cli/accounts-cli.ts:327-330.
The new serializer checks only whetherparsePreferenceAccount(text)succeeds. It does not check the parsed selector kind or value. For a unique stored OAuth emailaccount:team@example.com, selecting the row byid:writesaccount:team@example.com. Startup parses that text as an account-id selector forteam@example.com, not an email selector for the selected row. The preference then disappears or targets another row with that account id. An emailemail:team@example.comsimilarly loses the leadingemail:from its actual address. These addresses are accepted by the existing explicit-email grammar. This is the same colon-bearing-email writer path introduced by this PR; it is not a base writer defect.
Require the parsed selector to havekind === "email"and the complete intended address before choosing an email representation. Otherwise tryemail:${address}. Add writer-to-startup regressions for both prefix-bearing addresses, selected by row ID. Assert that the resulting selector resolves to the original row, not only that startup accepts it.
Observed reproduction: A dependency-free Node probe extracted the two parser function bodies from this exact checkout and applied the writer's candidate-selection expression. odd:name@example.com round-tripped correctly. account:team@example.com produced {kind:"account",value:"team@example.com"}; email:team@example.com produced {kind:"email",value:"team@example.com"}. Both failed identity round-trip. The probe exited 0; it is a parser/serialization reproduction, not a full CLI integration run.
Prior review: The author reply at #6749 (comment) resolves ordinary colon-bearing emails, but not the prefix-bearing cases above. The duplicate-row finding is addressed by packages/ai/src/auth-storage.ts:3235-3257 and startup rejection in packages/coding-agent/src/sdk/session.ts:799-842. Later overlaps bypass the complete provider preference list rather than silently keeping its first entry. The schema now includes uniqueItems; the argument parser preserves = before an email's final @. The peer's old-head review was read; it was not dismissed.
CI: Exact-head package checks, schema validation, CLI smoke, builds, and targeted preference/startup/schema tests passed in https://github.com/Yeachan-Heo/gajae-code/actions/runs/38559913420. Root-check failed in check:autorouting-map; the evidence producer and aggregate failed downstream. The same catalog/tier-map defect occurs on dev in https://github.com/Yeachan-Heo/gajae-code/actions/runs/38543591385. It is not counted as this PR's blocker. gajae-approve-gate.py returned CI_FAILED with pending=[] and need_local=[]; no approval is claimed.
Scope: +974 / -263, 18 files. OCR range preview identifies 10 reviewable files, including the generated schema; their change count is below 800. All reviewable diffs were read. Areas: AI credential ordering, accounts CLI, global configuration/startup, SDK discovery, and schema generation.
Conventions: User-facing changelog fragments are updated. Released changelog sections and generated docs index are untouched. The schema generator changes alongside its output; exact-head schema CI passed. No labels.
Spec: #6748's ordered fallback, caps, and session-stickiness paths are present. The successful-write-to-same-account contract fails for the address cases above.
Checked and clean: Under-cap/list ordering; capped last resort; runtime selector precedence; sticky sessions; duplicate-row detection and cleanup; atomic no-write errors; global-only settings; generator/output correspondence.
Not assessed: Full local CLI/broker integration and real-provider usage requests. Package builds/tests are prohibited in this reviewer pod. CI tests do not establish the prefix-bearing identity regression. Concurrent cap overshoot remains a documented limitation.
Verdict: gajae.pr-review-verdict.v1 needs-human sha256:f13f0bee3fe8389fe2b6bc5cbf225c489bbc83fd2ddcd509fccbe75b28fb2de4 reviewer:critic reviewer-id:gajae-reviewer evidence:email-serialization-changes-selector-identity
PR body verdict line count=0, not updated. Body verdict line is owned by meilleur-art; not edited. Suggested verdict line: gajae.pr-review-verdict.v1 needs-human sha256:f13f0bee3fe8389fe2b6bc5cbf225c489bbc83fd2ddcd509fccbe75b28fb2de4 reviewer:critic reviewer-id:gajae-reviewer evidence:email-serialization-changes-selector-identity
…the same selector The serializer accepted any candidate text startup could parse, without checking what it parsed to. A stored email such as account:team@example.com was written bare and read back as an account-id selector, and email:team@example.com lost its leading prefix, so the preference vanished or targeted another row. Each candidate is now written only when parsePreferenceAccount returns the same kind and value, which sends both addresses to the email:<address> form. Constraint: the explicit email: grammar already accepts prefix-bearing addresses Confidence: high Scope-risk: narrow Tested: new account:/email: prefix cases fail on 74373c3 and pass here, asserting the written selector resolves to the selected row id; accounts-prefer, startup-auth-config, configured-preference, schema, logout-org tests (76/76); coding-agent check; check:tools Not-tested: broker-backed startup
|
Addressed #6749 (review) in d40c38a.
Regressions (selected by |
probepark
left a comment
There was a problem hiding this comment.
Review (head d40c38a, gajae-reviewer on behalf of probepark)
Blocking: None found in the code review. The previous selector-identity finding is resolved.
ocr: blocking 0 / nit 0
Resolved findings:
packages/coding-agent/src/cli/accounts-cli.ts:327-349checks both selector kind and complete value before persisting an email or account ID. Prefix-bearing addresses now retain their identity. The exact-head accounts-prefer CI log confirms bothaccount:andemail:address regressions pass. These cases exercise the temporary SQLite store, written YAML, startup discovery, and resolution back to the selected row. All 14 accounts-prefer cases passed, with zero failures.packages/ai/src/auth-storage.ts:3235-3257detects different selectors resolving to the same stored row.packages/coding-agent/src/sdk/session.ts:824-842closes storage and rejects startup. A later overlap bypasses the entire provider preference list, rather than applying one entry and silently discarding another.- The peer review's schema and equals-sign observations are addressed: the generator adds
uniqueItems, and argument parsing preserves=before an email's final@. The peer's outstanding review concerns the old22117eehead; no exact-head peer objection was present before submission. No peer review was dismissed.
Author response: #6749 (comment)
CI: Confirmed base failure, not all green. At 2026-10-14 13:18 UTC, python3 /opt/data/scripts/gajae-approve-gate.py 6749 --head d40c38a94ea0aec3b25d3d98ac48e9dfdc668580 returned CI_FAILED (exit 12), pending=[], need_local=[]. Both package checks, schema validation, targeted tests, CLI smoke, and builds passed at this head.
The root-check log fails in check:autorouting-map (exit 1): ten Bedrock/Gemini catalog keys lack tier-map curation. Dev run 38543591385 independently fails the same tier-map contract for those keys; catalog/tier-map files are untouched here. The evidence producer and final aggregate fail downstream from root-check. Approval uses the confirmed-base-failure exception, not an assertion that the aggregate passed.
Head run: https://github.com/Yeachan-Heo/gajae-code/actions/runs/38564830055
Head root-check: https://github.com/Yeachan-Heo/gajae-code/actions/runs/38564830055/job/115859613659
Accounts-prefer integration/regressions (12:50 UTC, exact head, exit 0): https://github.com/Yeachan-Heo/gajae-code/actions/runs/38564830055/job/115859612641
Base failure: https://github.com/Yeachan-Heo/gajae-code/actions/runs/38543591385
Scope: +991 / -263, 18 files. All 10 OCR range-preview reviewable files were read (512 changed lines). Areas: AI credential ranking, accounts CLI, startup/settings parsing, SDK discovery, and schema generation/output. Excluded files are tests, documentation, and release fragments; none is runtime source.
Conventions: User-facing fragments present. Released changelog sections and generated docs index untouched. schemas/config.schema.json matches the changed generator; exact-head schema sync passed. No labels.
Notable:
packages/ai/src/auth-storage.ts:3277-3326orders usable under-cap entries before unlisted candidates, then capped entries.:3348-3360reads strategy-selected windows or every reported limit; unknown usage does not trigger a cap.packages/ai/src/auth-storage.ts:6039-6074retains a usable session account and applies the configured list only without an explicit/runtime selector or usable sticky account.packages/coding-agent/src/cli/accounts-cli.ts:352-414validates all targets and duplicates before the atomic write, preserving other providers.
Spec: #6748's ordered fallback, per-entry caps, portable persistence, precedence, and sticky-session contracts are represented by the paths above. Concurrent cap overshoot remains the documented soft-cap limitation.
Checked and clean: All reviewable diffs; prior own and peer findings; identity round-trip; duplicate-row startup rejection and cleanup; cap-window selection; blocked-account fallback; atomic no-write errors; global-only preferences; schema provenance. Implementation and tests change together; the description and follow-up response explain the format change and regressions.
Not assessed: Full local CLI/broker execution and real-provider usage requests. Builds/tests are prohibited in this reviewer pod. Exact-head CI provides independent temporary-store integration evidence, not real-account end-to-end verification. Concurrent reservations are not implemented or claimed.
Verdict: gajae.pr-review-verdict.v1 merge-approved sha256:ba648a79b6b48c5340526abf2b6a3f7c236c5148b91908f8e5ae5437b044f3bd reviewer:human reviewer-id:probepark evidence:selector-identity-fixed;exact-head-store-roundtrip-ci-pass;base-tier-map-failure-38543591385
PR body verdict line count=0, not updated. Body verdict line is owned by meilleur-art; not edited. Suggested verdict line: gajae.pr-review-verdict.v1 merge-approved sha256:ba648a79b6b48c5340526abf2b6a3f7c236c5148b91908f8e5ae5437b044f3bd reviewer:human reviewer-id:probepark evidence:selector-identity-fixed;exact-head-store-roundtrip-ci-pass;base-tier-map-failure-38543591385
Blocking findings resolved; superseded by the follow-up verdict.
probepark
left a comment
There was a problem hiding this comment.
Blocking findings resolved; stale review 5517890653 is dismissed.
Current verdict: blocking=0, as recorded in the exact-head review:
#6749 (review)
ocr: blocking 0 / nit 0
This is an administrative follow-up to that review, not a new code review or CI-green claim. The existing approval remains unchanged. The PR body and other reviewers' reviews are unchanged.
probepark
left a comment
There was a problem hiding this comment.
Code review (CI still running on 38fd6d3; approval decision follows once CI finishes).
Review (head 38fd6d3, gajae-reviewer on behalf of probepark)
Blocking: None found in the code review. Approval remains held pending exact-head CI.
ocr: blocking 0 / nit 0
CI: At 2026-10-15 16:13 UTC, python3 /opt/data/scripts/gajae-approve-gate.py 6749 --head 38fd6d3d9556c7847f484346bfc0645659b9e501 returned HOLD_CI (exit 10): 46 pending entries, including the not-yet-reported aggregate; failed=[]. Coding-agent checks, schema validation, targeted tests, builds, and root-check remain pending. The AI package check passed. These observations concern this head, not the previous head's successful tests.
Run: https://github.com/Yeachan-Heo/gajae-code/actions/runs/38763483935
Scope: +991 / -263, 18 files. Read all 10 range-preview reviewable diffs: 512 changed lines. Areas: credential ordering, accounts CLI, settings/startup parsing, SDK discovery, schema generator and output. Excluded paths contain tests, documentation, and release fragments; none is runtime source. Applied the OCR delegate rules and ran the required OCR stamp script successfully.
Conventions: Both user-facing changelog fragments are updated. Released changelog sections and generated docs index are untouched. schemas/config.schema.json changes accompany scripts/generate-json-schemas.ts; bun run generate-schemas is the generator and exact-head schema CI must establish synchronization. No labels.
Notable:
packages/ai/src/auth-storage.ts:3277-3326resolves each configured row, rejects semantic duplicate lists, and partitions usable under-cap entries, unlisted candidates, and capped entries in that order.:3348-3360reads strategy-selected windows or all reported limits; unknown usage does not count as full.packages/ai/src/auth-storage.ts:6039-6074applies configured ordering only without an explicit selection, runtime preference, or usable sticky session account. Blocked session accounts permit configured selection again.packages/coding-agent/src/cli/accounts-cli.ts:323-352preserves both selector kind and full value when serializing portable identities. The previous prefix-bearing email defect is not present: bare forms that change identity fall back to explicitemail:forms.:390-417resolves all requested rows and rejects duplicates before the atomic settings update.packages/coding-agent/src/session/startup-auth-config.ts:135-176validates caps and normalized selector duplicates.packages/coding-agent/src/sdk/session.ts:796-842finishes duplicate-row validation after local or broker reload, and closes storage on rejection.
Previous reviews: Review 5518526442 now reports this commit_id, but its submission predates this head's committer timestamp (2026-10-15 15:41:46 UTC). It is a rebound old-head approval, not verification of this head. The peer's outstanding CHANGES_REQUESTED review concerns 22117ee; its email serialization and duplicate-account findings are addressed by the paths above. No other review was dismissed or modified.
Spec: #6748's ordered fallback, per-account soft caps, portable persistence, duplicate rejection, and sticky-session requirements map to the paths above. The removal of the global threshold concerns an unreleased format, as the issue and description explain. Concurrent overshoot remains an explicitly documented soft-cap limitation.
Checked and clean: All reviewable diffs; selection precedence; blocked-account fallback; cap-window handling; serializer/parser identity; atomic no-write error paths; global-only settings; duplicate startup cleanup; generator/output correspondence. Test file names cover configured selection, CLI persistence, startup parsing, and schema contracts. The implementation/test changes are explained in the description and author follow-up.
Not assessed: No local builds or tests were run; they are prohibited in this reviewer pod. Current-head store/YAML integration, broker execution, and real-account behavior are not independently established yet. Author results and previous-head CI are not substitutes for this head's pending jobs. Re-run the approval gate after those jobs finish.
PR body verdict line count=0, not updated. The author's description remains unchanged. No merge-readiness claim or approval is made.
probepark
left a comment
There was a problem hiding this comment.
Code review (CI still running on 624830f; approval decision follows once CI finishes).
Review (head 624830f, gajae-reviewer on behalf of probepark)
Blocking: None found in step 3. Approval is held pending exact-head CI.
ocr: blocking 0 / nit 0
CI: python3 /opt/data/scripts/gajae-approve-gate.py 6749 --head 624830f0eb07d0cf9b2008a78022414af0e8ecc8 returned HOLD_CI (exit 10). The affected-path aggregate is not reported. The gate reports no failed checks, but neither package check is covered at this head. It lists cd packages/ai && bun run check and cd packages/coding-agent && bun run check as required if the eventual CI plan omits them. gh pr checks currently exposes only a passing local-public-surfaces check and a skipped deployed-release check. These do not establish package, schema, or test success.
Visible run: https://github.com/Yeachan-Heo/gajae-code/actions/runs/38769476389
Scope: +991 / -263, 18 files. Read all 10 range-preview reviewable diffs: credential ordering, accounts CLI/help, settings/startup parsing, SDK discovery, and schema generator/output. Excluded paths are tests, docs, and release fragments; none is runtime source. OCR delegate preview/rule and the required stamp script completed successfully.
Conventions: User-facing changelog fragments are updated. Released history and the generated docs index are untouched. schemas/config.schema.json changes accompany scripts/generate-json-schemas.ts; regenerate with bun run generate-schemas. Exact-head schema sync remains unverified. No labels.
Notable:
packages/ai/src/auth-storage.ts:3277-3326rejects semantic duplicate preference lists and partitions under-cap entries, unlisted candidates, and capped entries.:3334-3360excludes blocked/non-OAuth candidates and reads strategy-selected or provider-reported usage windows.packages/ai/src/auth-storage.ts:6039-6074retains usable sticky sessions and applies configured ordering only without an explicit/runtime selector or usable session account.packages/coding-agent/src/cli/accounts-cli.ts:323-352preserves selector kind and full value during portable serialization. Prefix-bearing email addresses fall back to an explicit email selector.:390-417resolves all entries and rejects duplicate rows before the atomic settings write.packages/coding-agent/src/session/startup-auth-config.ts:135-176validates caps and normalized selector duplicates.packages/coding-agent/src/sdk/session.ts:796-842completes duplicate-row validation after local or broker reload and closes storage on rejection.
Previous findings: The peer's outstanding CHANGES_REQUESTED concerns 22117ee. Its serializer and duplicate-account findings are addressed by the paths above. The author explains the fixes at #6749 (comment) and #6749 (comment). No peer review was dismissed. Review 5518526442 now carries this commit_id, but its submission precedes this head's committer timestamp, 2026-10-15T16:27:51Z. It is a rebound old-head approval, not verification of this head.
Spec: #6748's ordered fallback, per-account soft caps, portable persistence, duplicate rejection, and session-stickiness requirements map to the paths above. Concurrent cap overshoot remains the documented soft-cap limitation.
Checked and clean: All reviewable diffs; selector identity; ranking precedence; blocked-account fallback; cap-window handling; atomic no-write errors; global-only configuration; duplicate startup cleanup; generator/output correspondence. Test paths cover selection, CLI persistence, startup parsing, and schema contracts. The description explains the implementation/test changes and unreleased-format replacement.
Not assessed: No local builds or tests were run; they are prohibited in this reviewer pod. Exact-head store/YAML integration, broker behavior, real-provider usage, and schema synchronization are not independently established. Prior-head CI and author results are not substitutes. Re-run the approval gate after exact-head CI finishes.
PR body verdict line count=0, not updated. The author's description remains unchanged. No approval or merge-readiness claim is made.
probepark
left a comment
There was a problem hiding this comment.
Review (head 624830f, gajae-reviewer on behalf of probepark)
Blocking: No code blockers in the completed exact-head review: #6749 (review). This is a gate-only follow-up; the diff was not re-reviewed.
ocr: blocking 0 / nit 0
CI / approval: Approve held: CI plan did not run cd packages/ai && bun run check or cd packages/coding-agent && bun run check on 624830f. Run both commands at this head (or add them to the CI plan) and re-request review. Include the exact SHA, commands, and exit codes in the response.
python3 /opt/data/scripts/gajae-approve-gate.py 6749 --head 624830f0eb07d0cf9b2008a78022414af0e8ecc8 returned NEED_LOCAL (exit 11). It reports pending=[], failed=[], covered=[]. Affected path validation never reported on this head. Zero pending checks does not establish package validation. Earlier-head CI and the description's earlier-head local results are stale for this decision. Local builds/tests are prohibited in this reviewer pod.
The old approval 5518526442 was submitted before this head's committer timestamp, 2026-10-15T16:27:51Z. GitHub rebound it to this commit; it is not an approval of this head. The prior exact-head code review addressed the old-head peer findings. No new human responses or review-thread comments were found.
PR body verdict line count=0, not updated. The author's description remains unchanged. No approval, CI-green assertion, or merge-readiness claim is made.
|
Local checks at exact head
The worktree stayed clean afterward. — |
probepark
left a comment
There was a problem hiding this comment.
Review (head 624830f, gajae-reviewer on behalf of probepark)
Blocking: None in the completed exact-head code review: #6749 (review). This is the reply1 approval follow-up, not a second diff review.
ocr: blocking 0 / nit 0
CI / verification: At 2026-10-15 18:42–18:43 UTC, python3 /opt/data/scripts/gajae-approve-gate.py 6749 --head 624830f0eb07d0cf9b2008a78022414af0e8ecc8 returned ALLOW (exit 0). It reports pending=[], failed=[], need_local=[], and coverage for both package checks. This is an approval-gate result, not a claim that every possible integration scenario ran.
The maintainer supplied the missing exact-head results at #6749 (comment):
cd packages/ai && bun run check— exit 0; Biome checked 599 files.cd packages/coding-agent && bun run check— exit 0; Biome checked 3551 files, docs index andtsc --noEmitpassed.
The report names this full head SHA, a clean Linux worktree, and frozen-lockfile installation. need-local satisfied by #6749 (comment). These are maintainer-reported local results; I did not run builds or tests in this reviewer pod. The earlier-head results in the description are not evidence for this decision.
Scope: +991 / -263, 18 files. The completed review read all 10 range-preview reviewable diffs. Areas: AI credential ordering, accounts CLI/help, global settings/startup parsing, SDK discovery, and schema generator/output. The required OCR rule/stamp command succeeded again on this head.
Conventions: User-facing release fragments are present. Released history and the generated docs index are untouched. The generated config schema accompanies its source, scripts/generate-json-schemas.ts; regeneration uses bun run generate-schemas. No labels.
Notable / resolved findings:
packages/coding-agent/src/cli/accounts-cli.ts:323-352preserves selector kind and full value when serializing portable account identities. This addresses the old-head colon-bearing-email finding, including prefix-bearing addresses.packages/coding-agent/src/session/startup-auth-config.ts:135-176checks syntax and normalized duplicates.packages/coding-agent/src/sdk/session.ts:796-842rejects duplicate resolved rows and closes storage on failure. This addresses the old-head semantic-alias finding.packages/ai/src/auth-storage.ts:3277-3360applies ordered soft caps and blocked-account filtering.:6039-6074preserves usable sticky sessions and explicit/runtime selector precedence.
The peer's outstanding CHANGES_REQUESTED review concerns22117ee; the completed exact-head code review already assessed and resolved its findings. No new peer objection or thread reply appeared after the held review. No other person's review was dismissed or changed.
Checked and clean: The prior exact-head review covers ordered fallback, cap windows, portable serialization, atomic no-write errors, duplicate startup cleanup, global-only preferences, and generator/output correspondence. The new response supplies both missing package checks. The old approval 5518526442 predates this head's committer timestamp, 2026-10-15T16:27:51Z; it is rebound evidence, so this review supplies a fresh exact-head decision.
Not assessed: Real-provider usage requests, full broker execution, and concurrent-session reservations. Concurrent cap overshoot remains the documented soft-cap limitation. This follow-up does not claim new end-to-end execution.
Verdict: gajae.pr-review-verdict.v1 merge-approved sha256:1a9602ed73dc52f7a752ad6567a3b887489395b73d48c98c4e1099210989fc00 reviewer:human reviewer-id:probepark evidence:exact-head-code-review-5536731590;need-local-comment-6199573934;approve-gate-allow
PR body verdict line count=0, not updated. Body verdict line is owned by meilleur-art; not edited. Suggested verdict line: gajae.pr-review-verdict.v1 merge-approved sha256:1a9602ed73dc52f7a752ad6567a3b887489395b73d48c98c4e1099210989fc00 reviewer:human reviewer-id:probepark evidence:exact-head-code-review-5536731590;need-local-comment-6199573934;approve-gate-allow
snowykr
left a comment
There was a problem hiding this comment.
Verdict
APPROVED
Summary
The PR expands credential preferences to ordered per-provider account lists with optional per-account usage caps and a CLI writer. I found no verified actionable defects at the reviewed head.
Findings / Required Changes
No blocking or actionable findings.
CI / Verification
At the reviewed head, the changed auth-storage, accounts-prefer, startup-auth-config, and generated-schema test checks passed. The Affected path validation and Virtual integration validation checks also passed. Opt-in and platform-conditional checks were skipped as configured; no failed product check was observed. Reviewers did not rerun tests or builds.
Axis Coverage
| Axis | Verdict | Coverage |
|---|---|---|
| A1 — Intent / Policy / Contract | APPROVED | Ordered scalar/list preferences and per-entry caps match the updated schema, docs, and configuration contract. |
| A2 — Architecture / Correctness / Failure | APPROVED | Traced preference parsing, selection, cap boundaries, fallthrough, and session behavior; no reachable regression established. |
| A3 — Security / Privacy / Trust | APPROVED | Preferences reorder existing OAuth accounts without granting new credential authority or exposing credential payloads. |
| A4 — Verification / Tests / CI | APPROVED | Changed behavior has focused tests and relevant exact-head CI checks passed; no explicit verification requirement was shown to be violated. |
| A5 — Context / Compatibility / Platform | APPROVED | Traced settings, schema, CLI, SDK, and auth-storage consumers; the removed global cap is explicitly replaced by per-entry caps, and no material reuse defect was found. |
Closes #6748. Builds on #6733 (unreleased), widening
auth.credentialPreferencesfrom one preferred account to an ordered list with per-account usage caps.What changes for users
The same config can be written without opening YAML:
account: <percent>.gjc accounts pin.email:andaccount:selectors still work.id:stays rejected.auth.credentialPreferenceMaxUsedPercentis removed. The per-account cap replaces it. Neither key has shipped in a release, so nothing needs migrating. The two existing changelog fragments are rewritten to describe the final feature rather than a change to an unreleased one.Design choices aimed at keeping setup cheap
shared@example.com: 75). There is no separate threshold key, and no{ selector, maxUsedPercent }object form to remember.gjc accounts preferinstead of hand-editing. It accepts the same selectors aspinand resolves each one against the stored OAuth rows. It stores the row's email, or its account id when another row shares the email, so the file never holds a store-bound row id. It writes nothing if any account is missing, ambiguous, duplicated, or has a bad cap, and it prints the resulting order.id:selector, a bad cap, two accounts in one entry, or the same account listed twice is a typedinvalid-credential-preferenceserror. None of them is silently ignored.Selection rules
#applyConfiguredCredentialPreferences(formerly#applyConfiguredCredentialPreference) reorders the ranked candidates into three groups:Blocked and missing entries keep their ranked position. Usage is fetched only for capped entries that ranking did not already check. The reorder runs only when the session has no usable account of its own. A session keeps its account after crossing a cap and moves to the next listed account only when a quota or rate-limit failure blocks it. That matches the existing reason ranking is skipped mid-session (prompt cache).
Provider coverage
The cap reuses the existing
#maxKnownUsedFraction, so no provider is special-cased:anthropic:5h/7d)openai-codex:primary/secondary)openai-codex caps read the windows that provider reportskimi-code)kimi-code caps read the windows that provider reportsa provider without usage reporting follows list order and treats caps as not reachedconfigured preference selects the designated OAuth row in a pool that also stores an API keyTests use fixture usage reports. Against real accounts, I ran
gjc accounts preferfrom this branch on a temporary copy of my credential store (Anthropic: three accounts, Codex: two). It resolved the accounts, wrote the short form shown above, and refused a=0cap and an unknown email. The temporary copy was deleted afterwards. No token was refreshed and nothing was sent to a model.Tests
packages/ai/test/auth-storage-configured-preference.test.ts(19 tests):--prefer-credentialprecedencepackages/coding-agent/test/accounts-prefer.test.ts(10 tests):resolveStartupAuthConfigid:input is stored as an email=100is stored uncapped7o, a duplicate, and an organization row with neither a unique email nor a unique account id, each verified to write nothing--clearpackages/coding-agent/test/startup-auth-config.test.ts:scripts/generate-json-schemas.test.ts:Verification
At
22117ee72, rebased on currentdev(28a21fe85):cd packages/ai && bun run checkcd packages/coding-agent && bun run checkbun run check:schemasbun scripts/changelog-fragments.ts checkcd packages/ai && bun test test/auth-storagecd packages/coding-agent && bun test test/startup-auth-config.test.ts test/startup-credential-pin-handoff.test.ts test/accounts-prefer.test.ts test/accounts-logout-org.test.ts test/accounts-cli-errors.test.ts test/account-inventory-usage.test.ts test/settings test/credential test/auth-storage test/accountscd packages/coding-agent && bun test test/credential-rotation-midturn.e2e.test.tsbun test scripts/generate-json-schemas.test.tsThe 5 timeouts are all in
credential-rotation-midturn.e2e.test.ts. They happened only when that file ran together with the rest of the set, while the machine's load average was above 30. The number varied between runs (2, then 5), and the failing cases took 5 to 37 s. The file passes 42/42 on its own and does not reference anything this PR changes. The new CLI tests set a 30 s timeout because each case opens the store and refreshes the offline model registry.Known limitation
Caps are read from the shared usage cache. Concurrent sessions that start before it refreshes can each pick the same account and push it somewhat past its cap. The docs state this. #6748 asks whether it is worth solving.