* fix(auth): cooldown inline api key billing failures
* fix: mark inline api key prompt failures
* fix(auth): propagate inline api key cooldown to provider visibility
* fix(types): fix test type errors in agents and ui
* fix(auth): correct null check for cooldown gate and fix test case
* fix(ui): remove unrelated test drift and fix expired toast expectation
* fix(auth): preserve inline cooldown usageStats in warm worker snapshots
* test(auth): update warm snapshot expectation with usageStats
* test(auth): format inline cooldown worker proof
* fix(auth): narrow inline cooldown classifier
* fix(auth): mark managed file/exec SecretRef inline keys on auth/billing failure
The inline-api-key cooldown marker only fired for literal, env-marker, and
env SecretRef sources, while the resolution gate already covered managed
file/exec SecretRef provider keys via coerceSecretRef. As a result a
file/exec SecretRef models.providers.<id>.apiKey could never record a 402
billing cooldown, so the gate that would honor it never triggered.
Align the runner failure-marker trigger with the gate predicate
(isConfigBackedInlineProviderApiKey) so managed non-env SecretRef inline keys
are marked on auth/billing failure. Adds focused regression coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor(auth): drop now-unused isInlineProviderApiKeyAuth export
The runner failure-marker now gates on isConfigBackedInlineProviderApiKey, so
isInlineProviderApiKeyAuth has no remaining callers. Removing the dead export
also trims one public callable export back under the plugin-SDK surface budget.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(auth): enforce inline cooldown for managed file/exec SecretRef keys
The inline-api-key cooldown was recorded for managed (file/exec) SecretRef
provider keys but never enforced: these keys resolve through the synthetic
runtime path (resolveSyntheticLocalProviderAuth) and the explicit api-key
override path, neither of which consulted the inline-key cooldown gate. So an
exhausted file/exec SecretRef provider kept resolving and reporting available
during an active inline-api-key:<provider> cooldown.
Gate both resolution exits (and the provider-availability check) on
isConfigBackedInlineProviderApiKey + assertInlineProviderApiKeyUsable, matching
the literal/env paths. Local no-auth markers are not config-backed inline keys,
so they stay untouched. Adds regression coverage for both resolution paths.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(auth): gate runtime SecretRef cooldown checks
* fix(auth): honor inline key cooldowns in model-list and tool auth checks
Direct hasRuntimeAvailableProviderAuth callers omitted the auth store, so
inline provider keys in billing cooldown were still advertised as available
in model browsing (models.list) and model-backed tool auth checks. Pass the
scoped auth store at each call site and cover the cooled-key paths with
regression tests, including profile fallback and cooldown expiry.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(auth): resolve merge CI failures (format + browse cooldown mock independence)
- failover-retry-controller.ts: apply oxfmt formatting missed after the port.
- model-auth-availability.ts: the browse-side inline cooldown check imported
resolveInlineProviderApiKeyUnusableUntil from the auth-profiles usage module,
which several command/directive tests partially mock — the new export was
absent from those mocks, so every mocking test threw at the browse path.
Read the inline cooldown via usage-state primitives (resolveProfileUnusableUntil
+ isAuthCooldownBypassedForProvider) and build the usage id with the same
normalizeProviderId the write side uses, so the hot browse path no longer
depends on the mocked usage module while keeping key resolution identical.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(auth): make model-auth inline cooldown reads mock-independent
resolveApiKeyForProvider/assertInlineProviderApiKeyUsable read the inline
provider API-key cooldown, which this PR routes through the auth-profiles
usage module. Many existing suites partially mock that barrel (e.g.
run.overflow-compaction.test.ts) without the new export, so those tests
threw once merged code reached the inline path.
Read the cooldown via usage-state primitives (resolveProfileUnusableUntil +
isAuthCooldownBypassedForProvider) and build the `inline-api-key:<provider>`
usage id with the same normalizeProviderId the write side uses, so model-auth
no longer depends on the mocked usage module while key resolution stays
identical. Verified across all 30 suites that mock the auth-profiles module.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(test): use main's terminal failure shape for the inline-key billing case
Main reworked EmbeddedRunAttemptResult so attempt failures are reported via
`terminal: { kind: "failed", source, error }` instead of the old
`promptError`/`promptErrorSource` fields. The inline-key billing regression
test still used the removed fields, which merged cleanly but broke
check-test-types.
Switch it to the same shape the sibling helper in this file already uses.
Verified with `pnpm check:test-types` (exit 0).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Mert Basar <MertBasar0@users.noreply.github.com>
Co-authored-by: Ceviz Agent <ceviz@openclaw.local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
A provider credential present only in the process environment — named in
neither the provider entry, `auth.profiles`, nor `auth.order` — was appended to
the auth attempt list behind the operator's declared profiles. A run that left
a declared profile could therefore continue on an undeclared credential, which
may bill a different account, with no configuration authorizing the transition
and nothing reporting that it happened.
Add an explicit `authorization: "declared" | "ambient"` fact on direct auth
sources and enforce it during source selection. The field is required rather
than defaulted so every construction site is audited; `evidence` is left as
provenance, since a declared credential can legitimately be environment-sourced
via a `${VAR}` marker or a SecretRef.
An ambient credential may still serve a provider with no declared profiles (the
documented zero-config `PROVIDER_API_KEY` path), but it is no longer admitted
behind declared profiles, nor substituted for declared profiles that turned out
to be unusable. `auth.order` failover is unaffected: profiles are never filtered
by this change. The read-only availability evaluator applies the same rule so
status cannot advertise a credential the runtime refuses.
This restores the invariant that held before 1b1cebfe42 (#104685), where an
environment candidate was reachable only when a provider had no declared
profiles. The regression shipped in 2026.7.2-beta.1 onwards; no GA release is
affected.
Reported in #117956.
Claude-Session: https://claude.ai/code/session_01Nwk2KaB6zL71xa1i3Xwgwi
Co-authored-by: Marvinthebored <262704729+Marvinthebored@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix(googlechat): align doctor policy ownership with canonical account configuration
* test(googlechat): align generated channel metadata and typed doctor fixtures