Commit Graph
215 Commits
Author SHA1 Message Date
CodexandCodex 4533ae8850 fix(openai): preserve resettable quota exhaustion state
Co-authored-by: Codex <noreply@openai.com>
2026-07-29 14:48:23 +08:00
zyycnandCodex bf4da7a3e7 fix(accounts): unify credential and quota state
Apply OpenAI reauthorization to the selected local account ID while atomically replacing its verified upstream identity. Converge successful quota observations across refresh paths and preserve explicitly disabled account state during connection checks, quota refreshes, and credential rotation.

Co-authored-by: Codex <noreply@openai.com>
2026-07-29 03:51:54 +08:00
zyycnandCodex 0fe354b083 fix(openai): preserve requested service tier
Restore 2.x semantics: a request-specified tier drives persisted display and pricing, while the upstream response tier remains separately observable. Cover HTTP SSE and WebSocket conflicts plus the response-only fallback.

Co-authored-by: Codex <noreply@openai.com>
2026-07-29 03:09:22 +08:00
zyycnandCodex 2e861c03c4 fix(settings): apply refresh margin immediately
Schedule token refreshes at the exact configured lead time and reschedule existing refreshable accounts when that setting changes. Preserve current retry schedules when unrelated settings are saved.

Co-authored-by: Codex <noreply@openai.com>
2026-07-29 02:34:25 +08:00
zyycnandCodex d5fc778ea7 fix(openai): reconcile account connection failures
Expose exact upstream responses to authenticated connection tests, reload the resulting account state, and classify usage-limit windows as resettable quota exhaustion. Restore non-quota availability after verified credential rotation and refresh quota after reauthorization.

Co-authored-by: Codex <noreply@openai.com>
2026-07-29 02:31:38 +08:00
zyycnandCodex 851f473b88 fix(store): persist continuation affinity before delivery
Remove the asynchronous continuation queue so Core waits for the bounded Redis write before exposing a reusable response ID. Keep Redis index maintenance bounded per record to protect terminal response latency.

Co-authored-by: Codex <noreply@openai.com>
2026-07-29 01:35:05 +08:00
zyycnandCodex 5544b0fb29 fix(openai): separate quota and credential auth state
Keep quota probes from terminally invalidating OAuth accounts on 401 or 403, and stop manual quota refreshes from querying upstream with an expired access token. Model quota-owned transitions explicitly and cover both boundaries with regression tests.

Co-authored-by: Codex <noreply@openai.com>
2026-07-29 01:09:07 +08:00
zyycnandCodex 428e744710 test(providers): remove destructive live fixtures
Delete secret-dependent integration tests that consume live quota, register upstream tasks, or rotate OAuth refresh tokens. Keep the default workspace suite entirely runnable without ignored cases.

Co-authored-by: Codex <noreply@openai.com>
2026-07-29 01:06:09 +08:00
zyycnandCodex 7b406765d6 fix(store): harden execution observation buffering
Log direct Store failures without changing client-visible results, emit connection-test diagnostics before asynchronous persistence, and keep Provider errors intact when observation writes fail. Add a process-wide byte budget, cumulative queue statistics, ordered failure isolation, and bounded shutdown draining to the PostgreSQL observation worker.

Co-authored-by: Codex <noreply@openai.com>
2026-07-29 00:55:33 +08:00
zyycnandCodex 92ae7df115 fix(openai): bind reauthorization to stable account identity
Co-authored-by: Codex <noreply@openai.com>
2026-07-29 00:45:43 +08:00
zyycnandCodex cc42e78334 refactor(store): buffer data-plane side effects
Move PostgreSQL execution observations and Redis release, circuit, and continuation writes onto bounded non-blocking queues. Supervise independent flush workers so database or coordination failures can drop recoverable side effects without delaying protocol delivery.

Co-authored-by: Codex <noreply@openai.com>
2026-07-29 00:11:26 +08:00
zyycnandCodex 0c90ed30fb fix(proxy): restore transparent wire semantics
Preserve raw request and response data across HTTP, SSE, and WebSocket adapters, including opaque headers and upstream error envelopes. Remove private payload and catalog gates, keep account switching behind the pre-delivery barrier, and make observation failures unable to replace client-visible protocol data.

Co-authored-by: Codex <noreply@openai.com>
2026-07-29 00:10:35 +08:00
zyycnandCodex 5a81856510 fix(openai): preserve opaque response IDs
Treat Codex response handles as opaque strings across request decoding, continuation affinity, and upstream metadata. Store raw UTF-8 bytes without the PostgreSQL text index so long or NUL-containing handles cannot fail after a successful response.

Co-authored-by: Codex <noreply@openai.com>
2026-07-29 00:07:07 +08:00
zyycnandCodex eac1b2c39e fix(xai): classify upstream failures by scope
Co-authored-by: Codex <noreply@openai.com>
2026-07-28 18:31:06 +08:00
zyycnandCodex 337bb80230 fix(openai): restore streaming failure fallback
Restore the v2.x stream commit policies so ordinary requests forward the first event immediately while continuation and same-batch failures remain behind the retry boundary. Preserve typed upstream failure batches through Core and both streaming adapters, with a 64 KiB prefetch limit and end-to-end regression coverage.

Co-authored-by: Codex <noreply@openai.com>
2026-07-28 17:29:15 +08:00
zyycnandCodex 82f2a0da06 fix(openai): preserve quota exhaustion state
Prefer explicit quota evidence over generic 429 feedback, keep exhausted and terminal accounts from being downgraded by temporary rate limits, and reconcile historical cooldown states from current quota observations.

Co-authored-by: Codex <noreply@openai.com>
2026-07-28 14:47:21 +08:00
zyycnandCodex f5c1d773d7 fix(accounts): use current credential revisions
Load the current credential revision server-side for reauthorization and manual refresh so background token refreshes do not make admin mutations fail stale client fences.

Co-authored-by: Codex <noreply@openai.com>
2026-07-28 13:04:12 +08:00
zyycnandCodex a7d8c3d3df fix(accounts): make fixed diagnostics resilient
Co-authored-by: Codex <noreply@openai.com>
2026-07-28 09:45:35 +08:00
zyycnandCodex 997eb86aa0 fix(openai): refresh expired OAuth access tokens
Force refresh scheduling for OAuth accounts whose access token has already expired, even when a persisted next_refresh_at points to the future.

Co-authored-by: Codex <noreply@openai.com>
2026-07-28 09:41:44 +08:00
zyycnandCodex 38477f4328 fix(openai): normalize OAuth import credentials
Import OAuth accounts from access and refresh tokens only, then verify identity and observe the initial quota through official endpoints.

Co-authored-by: Codex <noreply@openai.com>
2026-07-27 23:54:59 +08:00
zyycnandCodex 5168f6a924 fix(openai): replace stale passive rate-limit snapshots
Treat each passive response-header observation as a complete rate-limit bucket snapshot, preventing stale secondary windows from surviving into a newer primary-only update.\n\nExtract duplicate OAuth callback parameter rejection to keep the release quality gate warning-free.

Co-authored-by: Codex <noreply@openai.com>
2026-07-27 23:12:17 +08:00
zyycnandCodex 6901e0ed45 fix(openai): accept standard OAuth callback parameters
Accept standard OAuth callback parameters while keeping one-shot flow and duplicate security parameter rejection.

Classify unavailable identity verification correctly and add redacted OAuth/import failure diagnostics.

Co-authored-by: Codex <noreply@openai.com>
2026-07-27 22:39:01 +08:00
zyycnandClaude Fable 5 9758c420fa refactor(availability): converge account availability on the gateway-core enum
- gateway-admin: drop the mirrored 7-variant enum; re-export the core type
- gateway-api: serialize wire availability via core as_str (bytes unchanged)
- gateway-store: drop ProviderAccountAvailability and four identity mappers;
  rows parse through core's codec with the same error string; redis state
  validation reuses core parse instead of a literal 7-string set
- providers/openai+xai: drop core<->admin identity mappers made moot by the
  admin re-export
- providers/xai: a single exhaustive From<GrokCredentialAvailability> impl
  is now the only Grok->core translation point

No behavior change: DB strings, wire JSON, and the AccountStatus fold
(including Invalid->Expired display) are byte-identical. 1244 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 20:40:47 +08:00
ZyyandCodex 40137b1205 fix(backend): preserve safe upstream error details
Keep admin account connection tests routed through the provider probe even when the stored account is already marked unavailable so clicking test can refresh state from the upstream response.

Preserve provider-declared safe upstream details when OpenAI-compatible API errors collapse local provider unavailability to the stable no-provider contract.

Co-authored-by: Codex <noreply@openai.com>
2026-07-27 17:09:59 +08:00
ZyyandCodex 2e9213a784 fix(backend): derive Codex metadata from provider catalogs
Share Codex Responses request semantics across OpenAI and xAI providers so reasoning effort, presets, request kind, subagent kind, and compaction observations stay consistent.

Read xAI reasoning efforts from official catalog feature options instead of model-name hardcoding, and make Codex service tiers data-driven through ModelPresentation rather than API adapter defaults.

Co-authored-by: Codex <noreply@openai.com>
2026-07-27 16:27:41 +08:00
ZyyandCodex c856e4306f fix(xai): mark rate limits as quota exhausted
Classify xAI 429 rate-limit responses as quota exhaustion so the scheduler removes the account from active rotation until the normal recovery flow makes it available again.

Cover the provider replay path and transport classification with tests.

Co-authored-by: Codex <noreply@openai.com>
2026-07-27 14:55:30 +08:00
ZyyandCodex 92090f28bf docs(backend): unify provider comments to Chinese
Convert the remaining English doc comments in the openai and xai provider
crates to Chinese, function-oriented wording so the whole backend follows
one convention. Explanatory narration is compressed into what the item does
and what its errors mean; code identifiers, route paths, rustdoc links and
`# Errors` headings stay as-is.

Also fixes two half-translated comments left in the websocket protocol
module and merges a duplicated doc block on the OIDC verifier constructor.
No code, signature or `#[error(...)]` string changes.

Co-authored-by: Codex <noreply@openai.com>
2026-07-27 13:13:44 +08:00
ZyyandCodex 12c2b612de chore(db): fold retention indexes into initial migration
Keep the database baseline as a single initial migration now that deployed instances have already been upgraded. The retention delete indexes move into 0001, the standalone 0002 file is removed, and the migration tests now expect one applied migration.

Co-authored-by: Codex <noreply@openai.com>
2026-07-27 12:05:49 +08:00
e8af0b9c66 chore(deps): Bump ed25519-dalek from 2.2.0 to 3.0.0 in /backend (#26)
* chore(deps): Bump ed25519-dalek from 2.2.0 to 3.0.0 in /backend

Bumps [ed25519-dalek](https://github.com/dalek-cryptography/curve25519-dalek) from 2.2.0 to 3.0.0.
- [Release notes](https://github.com/dalek-cryptography/curve25519-dalek/releases)
- [Changelog](https://github.com/dalek-cryptography/curve25519-dalek/blob/3.0.0/CHANGELOG.md)
- [Commits](https://github.com/dalek-cryptography/curve25519-dalek/compare/ed25519-2.2.0...3.0.0)

---
updated-dependencies:
- dependency-name: ed25519-dalek
  dependency-version: 3.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix: enable ed25519 PKCS8 allocation support

ed25519-dalek 3 gates private-key PKCS#8 encoding behind the alloc feature. Enable it alongside pkcs8 so existing credential export tests continue to compile and exercise the same behavior.

Co-authored-by: Codex <noreply@openai.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Zyy <No Email>
Co-authored-by: Codex <noreply@openai.com>
2026-07-27 12:04:48 +08:00
80776e8e03 chore(deps): Bump jsonwebtoken from 10.3.0 to 11.0.0 in /backend (#24)
* chore(deps): Bump jsonwebtoken from 10.3.0 to 11.0.0 in /backend

Bumps [jsonwebtoken](https://github.com/Keats/jsonwebtoken) from 10.3.0 to 11.0.0.
- [Changelog](https://github.com/Keats/jsonwebtoken/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Keats/jsonwebtoken/compare/v10.3.0...v11.0.0)

---
updated-dependencies:
- dependency-name: jsonwebtoken
  dependency-version: 11.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix: adapt jsonwebtoken 11 header extras API

jsonwebtoken 11 wraps non-standard JWT header fields in Extras. Keep the existing strict header validation by checking the wrapped inner map instead of calling is_empty directly.

Co-authored-by: Codex <noreply@openai.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Zyy <No Email>
Co-authored-by: Codex <noreply@openai.com>
2026-07-27 11:54:06 +08:00
dependabot[bot] 9fc162a82f chore(deps): Bump base64 from 0.22.1 to 0.23.0 in /backend (#25)
Bumps [base64](https://github.com/marshallpierce/rust-base64) from 0.22.1 to 0.23.0.
- [Changelog](https://github.com/marshallpierce/rust-base64/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/marshallpierce/rust-base64/compare/v0.22.1...v0.23.0)

---
updated-dependencies:
- dependency-name: base64
  dependency-version: 0.23.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-27 11:41:53 +08:00
dependabot[bot] 9532bd438b chore(deps): Bump getrandom from 0.3.4 to 0.4.3 in /backend (#27)
Bumps [getrandom](https://github.com/rust-random/getrandom) from 0.3.4 to 0.4.3.
- [Changelog](https://github.com/rust-random/getrandom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-random/getrandom/compare/v0.3.4...v0.4.3)

---
updated-dependencies:
- dependency-name: getrandom
  dependency-version: 0.4.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-27 11:41:49 +08:00
dependabot[bot] 73319f1943 chore(deps): Bump syn from 2.0.118 to 3.0.3 in /backend (#28)
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.118 to 3.0.3.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.118...3.0.3)

---
updated-dependencies:
- dependency-name: syn
  dependency-version: 3.0.3
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-27 11:41:45 +08:00
ZyyandCodex 45903b684b fix(admin): remove config revision fences from API writes
Operator mutations no longer require callers to carry configRevision values through accounts, client keys, provider credentials, or settings. The store and admin use cases now apply current state directly, which removes stale-revision failures from normal create, update, and delete flows.

xAI Responses catalog entries now let the upstream validate feature support instead of rejecting requests locally when optional catalog metadata is absent, matching the Build routing behavior used by grok2api.

Co-authored-by: Codex <noreply@openai.com>
2026-07-27 11:30:59 +08:00
zyycnandClaude 885369f19f chore(release): drop Windows target, extend stop grace, document single-replica
The Windows release shipped a zip that self-update cannot extract, so
remove the Windows platform, matrix leg, and its dead archive handling
rather than add zip support. Raise the compose stop grace period above the
serial drain plus worker-shutdown budget so Docker does not SIGKILL mid
shutdown. Record that the deployment is single-replica and why, since the
worker leader lease and self-update swap are per-replica.

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-27 02:26:25 +08:00
zyycnandClaude 24ff339c97 fix(admin): tolerate bad records in usage billing and stop showing invalid accounts as active
One record with an unparseable provider_kind no longer makes the whole
usage list return 500; the failing record is skipped and the list still
renders. An account whose credential availability is Invalid — excluded
from scheduling — was displayed as Active in the directory; it now derives
the Expired status so it reads as unavailable, consistent with how a dead
credential surfaces to the operator.

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-27 02:26:13 +08:00
zyycnandClaude e582eb73d2 fix(xai): preserve unknown fields, scope history sanitize, guard cache-route tools, batch selector reads
Rebuild history items with in-place Map surgery so unknown upstream fields
survive instead of evaporating through a field whitelist. Narrow history
sanitization to the known Grok-internal injection sites rather than
stripping every null and any key named phase at any depth. Ensure the
prompt-cache route's injected web_search/x_search tools can never be
actually invoked when the client sent its own tools. Batch the selector's
per-account observed-model-support and cooldown reads instead of a serial
Redis round trip each. One plan scope failing no longer aborts the whole
provider catalog refresh.

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-27 02:26:02 +08:00
zyycnandClaude ec252b3b46 fix(openai): per-account quota sync, narrower catalog union and expiry, model presentation
One account's quota parse failure no longer aborts the whole synchronize
batch. The per-plan catalog union no longer wedges every refresh on any
presentation/metadata drift — only routing-relevant facts must agree.
compile_model_capabilities now attaches a ModelPresentation so openai
models carry display facts through /v1/models instead of fabricated
defaults. is_expired_credential requires a genuine 401/403 or structured
expiry code instead of matching the substring "unauthorized" anywhere in
the body. The request turn-state header is authoritative over the body
camelCase alias.

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-27 02:25:47 +08:00
zyycnandClaude 6503f36750 fix(core): stop deadline expiry from tripping the circuit and unpin recovery account
A gateway-budget deadline was recorded as a provider Timeout that counts
against the circuit breaker, so many long streams expiring together could
wrongly open it; a deadline reached after downstream delivery has begun no
longer feeds the provider circuit. required_account was pinned for the rest
of the request after a same-account credential recovery, so a later
retryable error terminated instead of switching accounts; the pin now binds
only the immediate recovery replay and is cleared afterward, letting
subsequent errors use the remaining accounts and retry budget.

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-27 02:25:47 +08:00
zyycnandClaude b11eeac29e test: enforce the crate dependency DAG and widen websocket route coverage
Add a workspace-level architecture test that parses each backend crate's
manifest and asserts the layering rules the architecture doc describes —
gateway-core has no HTTP/DB/Redis/provider dependency, the providers do not
depend on each other, gateway-admin has no transport or storage crates —
and that no production src file hosts a test module. Extend the responses
websocket tests to cover the upgrade boundary: handshake routing, upgrade
precedence over authentication, and malformed-handshake rejection.

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-27 01:51:51 +08:00
zyycnandClaude ad5f3ba997 fix(xai): tolerate unknown JWKS members and corrupt catalog cache; drop dead sync
Stop rejecting a JWKS document that carries members the struct does not
model, so a token still verifies when the IdP adds a field. Treat an
unparseable catalog cache entry as a cache miss that refetches instead of
a hard failure. Remove the unused GrokCredentialCatalogService::synchronize
cluster and its snapshot re-export.

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-27 01:51:41 +08:00
zyycnandClaude 15455ca619 fix(openai): parse Retry-After dates, stop dropping raw frames, restrict base_url
Retry-After now accepts the HTTP-date form in addition to integer seconds.
When a raw SSE passthrough frame fails to construct, fall back to the
serialized form instead of silently dropping the event. Reject upstream
base_url values that are http or resolve to loopback/private ranges, so an
operator-supplied URL cannot point the data plane at an internal address.

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-27 01:51:41 +08:00
zyycnandClaude 904315c209 fix(admin): clamp session ttl and de-duplicate the UTC+8 offset helper
An enormous session_ttl_minutes config value overflowed chrono when added
to the current time and panicked session creation; clamp it to a sane
maximum. Route both UTC+8 formatting call sites through a single helper.

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-27 01:51:30 +08:00
zyycnandClaude 95d720dd86 fix(host): close self-restart and shutdown races, drop dead preview channel
Retry binding the listener on AddrInUse within a bounded window so a
self-restart handoff that races the old process releasing the port does
not exit the new process offline. Make restart() take the same operation
lock as update and rollback so a restart cannot interleave with an
in-flight update. Enable the idle notification before reading the active
count in wait_until_idle so the last connection dropping between the read
and the first poll cannot lose the wakeup and stall the full drain
timeout. Remove the unreachable preview update-channel configuration.

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-27 01:51:30 +08:00
zyycnandClaude 74f59cc4f8 fix(core): correct attempt-observation reset, success status, and error precision
reset_uncommitted_observations left provider_processing_ms set when
discarding a failed attempt's observations, leaking one attempt's upstream
processing time into the next; clear it with the other per-attempt fields.
finish_success fabricated an upstream_status_code of 200 when none was
observed — persist the absent status as NULL instead. Give the "no active
attempt" internal state its own EngineError::NoActiveAttempt instead of
overloading EmptyRoutingPlan, and map it to an internal gateway error.
Remove the unused free function commit_requirement.

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-27 01:51:19 +08:00
zyycnandClaude e979454457 test(xai): move release-profile tests into the integration test target
The workspace runs tests only through each crate's main integration
target (autotests = false), so the #[cfg(test)] module embedded in
transport/profile.rs never executed — the only such module left in
production src. Mirror the two release-refresh tests into
tests/transport/profile.rs and export the release service surface they
exercise.

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-27 01:19:12 +08:00
zyycnandClaude 8226089ade fix(api): resolve the admin request id independently of the header name
The request-id layer injects under the configured api.request_id_header,
but the admin extractor read the hardcoded x-request-id name, so pointing
that setting at any other header made every authenticated admin request
fail with an internal error and took the admin interface down. Read the
name-agnostic RequestId extension the layer also populates, keeping the
header lookup as a fallback for callers that run without the layer.

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-27 01:09:49 +08:00
zyycnandClaude c275119f12 fix(admin): remove login throttling to close the lockout DoS
The failure-count lockout keyed on request source was the wrong control
for this deployment: behind the mandated reverse proxy every login shares
the proxy peer address, so an unauthenticated attacker could trip the
shared bucket and lock the real administrator out for the window,
renewably. Remove the throttle end to end — the LoginError::Throttled
state, the LoginCommand source, the AuthStore/AdminAuthStateRepository
failure-window ports and their Redis Lua script, and the login-source
middleware — and let a wrong password return InvalidCredentials directly.
Online brute-force is bounded by the Argon2 verification cost and admin
password strength; deployments wanting rate limiting apply it at the
reverse proxy, which sees real client IPs.

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-27 00:31:10 +08:00
zyycnandClaude 9322b7aeb8 fix(api,store): aggregate IPv6 login buckets by /64 and honor CI truthiness
Review follow-ups. The login throttle keyed on the full IPv6 /128 address,
so a direct-connect attacker with a routed /64 got an independent
five-guess bucket per address; bucket IPv6 sources by their /64 prefix so
the whole allocation shares one quota. IPv4 keying is unchanged, and a
router-level test pins both the aggregation and cross-prefix isolation.

The store test_env guard treated any present CI variable as CI, so a
developer running CI=false to signal not-CI turned skips into panics; parse
CI per the is-ci convention (present and not 0/false/empty).

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-27 00:17:02 +08:00
zyycnandClaude 55075677eb fix(xai): log every skipped import entry so silent credential loss is traceable
Follow-up to 97b91d34: on partial import success the first_failure was
discarded and skipped entries left no trace, so an entry whose refresh
token was already rotated inside verify (transient userinfo failure, local
rejection, or duplicate-subject skip) was destroyed upstream while the
response only reported the success count — the operator had no signal that
a credential needed re-authorization. Emit a warn for each skipped entry
naming the account and whether its token was already rotated.

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-27 00:11:30 +08:00