Repository navigation
fix(healthcheck): per-provider proactive-refresh skip list (rescue short-TTL OAuth like Kimi) - #3159
Conversation
…ort-TTL OAuth) Root cause of the stuck 'Token Expired' Kimi-coding account on the .15 box: OMNIROUTE_DISABLE_TOKEN_HEALTHCHECK=true was set (to keep the Codex/OpenAI rotating-token family off the proactive sweep after the #3019 Auth0 cascade). But that switch is all-or-nothing — it also stops proactively refreshing short-TTL providers like Kimi-coding, whose access tokens then expire while idle and only a (racy) reactive request can revive them. Add OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS (comma-separated, case-insensitive): checkConnection skips ONLY the listed providers, so an operator can set `codex,openai` to keep the cascade providers reactive-only while Kimi/Claude/etc. resume proactive refresh. Lets .15 drop the blunt full-disable. TDD: a listed provider triggers no proactive refresh; the same due connection IS refreshed once removed from the list (control proves the skip — not token freshness — gated it). Fails on the pre-fix sweep. Refs investigation: minimax=billing, glm=transient upstream, kimi=this, codex=Auth0 invalidation.
There was a problem hiding this comment.
Code Review
This pull request introduces the OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS environment variable, allowing specific providers to opt out of proactive token-refresh sweeps while keeping reactive refresh paths active. This change is documented in .env.example and ENVIRONMENT.md, implemented in tokenHealthCheck.ts, and verified with a new unit test. The review feedback suggests two key improvements: caching the parsed skip-providers set to optimize performance during connection sweeps, and wrapping the unit test logic in a try...finally block to guarantee environment variable cleanup and prevent test pollution in case of assertion failures.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| function getHealthCheckSkipProviders(): Set<string> { | ||
| const raw = process.env.OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS || ""; | ||
| return new Set( | ||
| raw | ||
| .split(",") | ||
| .map((s) => s.trim().toLowerCase()) | ||
| .filter(Boolean) | ||
| ); | ||
| } |
There was a problem hiding this comment.
Accessing process.env and parsing the comma-separated skip list on every single connection check is inefficient, especially in environments with hundreds of connections. Since checkConnection is called in a loop during sweep(), we can optimize this by caching the parsed Set and only re-parsing if the raw environment variable changes (which is useful for tests).
let cachedSkipProviders: Set<string> | null = null;
let lastRawSkipProviders: string | null = null;
function getHealthCheckSkipProviders(): Set<string> {
const raw = process.env.OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS || "";
if (cachedSkipProviders && raw === lastRawSkipProviders) {
return cachedSkipProviders;
}
const parsed = new Set(
raw
.split(",")
.map((s) => s.trim().toLowerCase())
.filter(Boolean)
);
cachedSkipProviders = parsed;
lastRawSkipProviders = raw;
return parsed;
}| test("checkConnection skips providers listed in OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS (#kimi-15)", async () => { | ||
| await resetStorage(); | ||
|
|
||
| const providerId = "custom-oauth-skip-list"; | ||
| const refreshRequests: string[] = []; | ||
| const prevSkip = process.env.OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS; | ||
|
|
||
| await withHttpServer( | ||
| (req, res) => { | ||
| let body = ""; | ||
| req.setEncoding("utf8"); | ||
| req.on("data", (chunk) => { | ||
| body += chunk; | ||
| }); | ||
| req.on("end", () => { | ||
| refreshRequests.push(body); | ||
| res.writeHead(200, { "Content-Type": "application/json" }); | ||
| res.end( | ||
| JSON.stringify({ | ||
| access_token: "should-not-be-fetched", | ||
| refresh_token: "should-not-be-fetched", | ||
| expires_in: 3600, | ||
| }) | ||
| ); | ||
| }); | ||
| }, | ||
| async (tokenServer) => { | ||
| await withPatchedProvider( | ||
| providerId, | ||
| { | ||
| tokenUrl: `${tokenServer.url}/token`, | ||
| clientId: "skip-client-id", | ||
| clientSecret: "skip-client-secret", | ||
| }, | ||
| async () => { | ||
| const connection = await providersDb.createProviderConnection({ | ||
| provider: providerId, | ||
| authType: "oauth", | ||
| name: "Skip-list Account", | ||
| email: "skip@example.com", | ||
| accessToken: "stale-access-token", | ||
| refreshToken: "refresh-token-skip", | ||
| isActive: true, | ||
| }); | ||
|
|
||
| // The connection is due for refresh (no known expiry, never checked). | ||
| // With the provider listed, the proactive sweep must skip it entirely — | ||
| // NO refresh request is made. | ||
| process.env.OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS = `foo, ${providerId} ,bar`; | ||
| await tokenHealthCheck.checkConnection(connection); | ||
| assert.equal( | ||
| refreshRequests.length, | ||
| 0, | ||
| "listed provider must NOT trigger a proactive refresh" | ||
| ); | ||
|
|
||
| // Control: with the provider no longer listed, the same due connection | ||
| // IS refreshed — proving the skip (not token freshness) gated it. | ||
| process.env.OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS = "some-other-provider"; | ||
| const stillStale = await providersDb.getProviderConnectionById((connection as any).id); | ||
| await tokenHealthCheck.checkConnection(stillStale); | ||
| assert.equal(refreshRequests.length, 1, "non-listed provider must refresh"); | ||
| } | ||
| ); | ||
| } | ||
| ); | ||
|
|
||
| if (prevSkip === undefined) delete process.env.OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS; | ||
| else process.env.OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS = prevSkip; | ||
| }); |
There was a problem hiding this comment.
If any assertion fails during the test execution (e.g., assert.equal(refreshRequests.length, 0)), the test will abort immediately, and the cleanup code at the end of the test will never run. This leaks the modified process.env.OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS to subsequent tests, causing potential test pollution. Wrap the test logic in a try...finally block to guarantee the environment variable is restored.
test("checkConnection skips providers listed in OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS (#kimi-15)", async () => {
await resetStorage();
const providerId = "custom-oauth-skip-list";
const refreshRequests: string[] = [];
const prevSkip = process.env.OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS;
try {
await withHttpServer(
(req, res) => {
let body = "";
req.setEncoding("utf8");
req.on("data", (chunk) => {
body += chunk;
});
req.on("end", () => {
refreshRequests.push(body);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
access_token: "should-not-be-fetched",
refresh_token: "should-not-be-fetched",
expires_in: 3600,
})
);
});
},
async (tokenServer) => {
await withPatchedProvider(
providerId,
{
tokenUrl: tokenServer.url + "/token",
clientId: "skip-client-id",
clientSecret: "skip-client-secret",
},
async () => {
const connection = await providersDb.createProviderConnection({
provider: providerId,
authType: "oauth",
name: "Skip-list Account",
email: "skip@example.com",
accessToken: "stale-access-token",
refreshToken: "refresh-token-skip",
isActive: true,
});
// The connection is due for refresh (no known expiry, never checked).
// With the provider listed, the proactive sweep must skip it entirely —
// NO refresh request is made.
process.env.OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS = "foo, " + providerId + " ,bar";
await tokenHealthCheck.checkConnection(connection);
assert.equal(
refreshRequests.length,
0,
"listed provider must NOT trigger a proactive refresh"
);
// Control: with the provider no longer listed, the same due connection
// IS refreshed — proving the skip (not token freshness) gated it.
process.env.OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS = "some-other-provider";
const stillStale = await providersDb.getProviderConnectionById((connection as any).id);
await tokenHealthCheck.checkConnection(stillStale);
assert.equal(refreshRequests.length, 1, "non-listed provider must refresh");
}
);
}
);
} finally {
if (prevSkip === undefined) delete process.env.OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS;
else process.env.OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS = prevSkip;
}
});* chore(release): open v3.8.10 development cycle Bump 3.8.9 → 3.8.10 across package.json, lockfile, electron, open-sse, and docs/reference/openapi.yaml; add the [3.8.10] CHANGELOG section (root + 41 i18n mirrors) as the integration target for the cycle. Entries land here as work merges into release/v3.8.10; finalized by the release flow. * fix(providers): resolve web provider alias collisions Assign unique aliases to HuggingChat, Kimi Web, and Qwen Web so they no longer shadow primary providers or trigger startup warnings. Add a unit test to enforce provider alias uniqueness and prevent future collisions. Also expand local ignore and VS Code exclude rules for agent, build, and worktree artifacts. * fix(responses): normalize image_url parts across input paths (#3150) Normalize image_url parts across all Responses input paths. Integrated into release/v3.8.10. * fix(api-manager): preserve API key expiration local time (#3146) Preserve API key expiration local time + clear button. Integrated into release/v3.8.10. * Strip previous_response_id for stateless Responses upstreams (#3143) Strip previous_response_id for stateless Responses upstreams (auto/strip/preserve). Integrated into release/v3.8.10. * fix(opencode-plugin): map thinking cap to interleaved in model+combo (#3138) Map caps.thinking to ModelV2.capabilities.interleaved for opencode-plugin. Integrated into release/v3.8.10. * fix(providers): use synced models as fallback for all providers (#3148) Use synced models as authoritative local catalog for all providers (+regression test). Integrated into release/v3.8.10. * fix(qoder): bifurcate validation by token type — PAT→Cosy, regular API key→dashscope (#3149) Bifurcate Qoder validation by token type (PAT→Cosy, regular→dashscope) +regression test. Integrated into release/v3.8.10. * fix(antigravity): dynamic model resolution via MITM alias table (#3144) Dynamic antigravity MITM model resolution in the executor (+bug fix +regression test; DB import dropped from client-reachable config). Integrated into release/v3.8.10. * Feature/batch allow big (#3128) Podman deployment options + larger upload body-size limits (+CONTAINER_HOST docs). Integrated into release/v3.8.10. * fix(fireworks): preserve fully-qualified router/model IDs (#3133) (#3160) Fireworks router IDs (accounts/fireworks/routers/...) were double-prefixed with accounts/fireworks/models/ → upstream 404. Add optional acceptedModelIdPrefixes to the registry entry and skip the prepend when the model already starts with an accepted prefix. Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com> * fix(llama-cpp): route to configured local baseUrl instead of OpenAI (#3136) (#3161) llama-cpp was missing from the local-provider group in buildUrl(), so it fell through to the OpenAI baseUrl and returned an OpenAI 401. Add the case to resolve the connection's providerSpecificData.baseUrl. Co-authored-by: tjengbudi <tjengbudi@users.noreply.github.com> * fix(t3-chat-web): parse cookies + convexSessionId from stored credential (#3007) (#3162) The executor read credentials.cookies/convexSessionId, but the pipeline only stores the pasted string under apiKey → t3.chat always 400'd. Parse both values from apiKey (fallback accessToken), mirroring validation.ts. Co-authored-by: minhtran162 <minhtran162@users.noreply.github.com> * fix(minimax): stop capping MiniMax-M3 / M2.7 max_tokens at 8192 (#3141) (#3163) MiniMax-M3 had no MODEL_SPECS entry and capitalized MiniMax-M2.7 missed its lowercase spec (case-sensitive lookup) → both fell to the 8192 default cap. Add the M3 spec (512K output), alias the capitalized ids, and make getModelSpec lookups case-insensitive. Co-authored-by: totaltube <totaltube@users.noreply.github.com> * fix(github-copilot): discover model catalog live from api.githubcopilot.com (#3120, #3121) (#3164) The github (Copilot) provider had a static hardcoded catalog with no discovery source, so Import Models never refreshed (#3120) and advertised non-entitled models that 400 on use (#3121). Add a live /models fetch with fallback to the static list. Co-authored-by: gabrielmoreira <gabrielmoreira@users.noreply.github.com> * fix(combo): invalidate nested-combo cache on edits + log DATA_DIR (#3147) (#3165) Editing a combo did not invalidate the 10s nested-combo expansion caches (chat.ts getCombosCachedForChat + chatCore.ts getCombosCached; the exported clearCombosCache was dead code), so a removed nested target/model could be served as a phantom for up to 10s. Wire a shared monotonic combos-cache version in readCache (bumped by invalidateDbCache("combos") on every combo write); both cache layers treat a version mismatch as a miss. Also log the resolved DATA_DIR/SQLITE_FILE absolute path at DB init so the reporter's 'persists across restart + volume wipe' symptom (a multi-replica Docker volume/DATA_DIR mismatch, not a routing bug) is diagnosable from logs. Includes consolidated CHANGELOG entries for #3133/#3136/#3007/#3141/#3120/#3121. Co-authored-by: ViFigueiredo <ViFigueiredo@users.noreply.github.com> * fix(web-tools): parse bare JSON tool calls (#3157) Parse bare JSON tool calls for deepseek-web (#2820) + fuzzy tool-name matching. Integrated into release/v3.8.10. * fix(misc): minor fixes across reasoning cache, account fallback, binary manager (#3177) Misc: ProviderProfile export, DeepSeek reasoning regex, binary guard. Integrated into release/v3.8.10. * fix(kiro): minor OAuth social exchange tweaks (#3176) Kiro social OAuth: optional targetProvider passthrough. Integrated into release/v3.8.10. * deps: bump hono from 4.12.18 to 4.12.23 (#3179) Bump hono to 4.12.23. Integrated into release/v3.8.10. * fix(providerRegistry): update kilocode format and executor (#3166) kilocode: openai format + default executor (matches kilo-gateway) + registry test. Integrated into release/v3.8.10. * feat(metrics): cross-request TTFT and gap latency after tool calls (#3173) Cross-request TTFT + gap-after-tool latency metrics (+test). Integrated into release/v3.8.10. * feat(dashboard): provider stats API endpoint and dashboard page (#3175) Provider stats dashboard + API (SQL moved to db module per Hard Rule #5, +test). Integrated into release/v3.8.10. * fix(usage): sequential+spaced OAuth quota sync, reactive force-refresh, actionable 401 (#3156) Sequential+spaced OAuth quota sync, reactive force-refresh on 401, actionable 401 in UI. Integrated into release/v3.8.10. * fix(healthcheck): per-provider proactive-refresh skip list (rescue short-TTL OAuth) (#3159) Per-provider proactive-refresh skip list (OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS) to rescue short-TTL OAuth. Integrated into release/v3.8.10. * feat(quota): show OAuth token expiry on provider cards (small, blue, informative) (#3178) Show OAuth token expiry on provider cards (small, blue, informative). Integrated into release/v3.8.10. * fix(providers): empty refresh must not resurface just-cleared synced models (#3181) Empty refresh must not resurface just-cleared synced models (fixes the release-blocking provider-models-route test). Integrated into release/v3.8.10. * chore(release): v3.8.10 — 2026-06-04 (finalize CHANGELOG) --------- Co-authored-by: Wilson <pedbookmed@gmail.com> Co-authored-by: Xiangzhe <32761048+xz-dev@users.noreply.github.com> Co-authored-by: Jan Leon <Jan.gaschler@gmail.com> Co-authored-by: M.M <mr.maatoug@gmail.com> Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com> Co-authored-by: Markus Hartung <mail@hartmark.se> Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com> Co-authored-by: tjengbudi <tjengbudi@users.noreply.github.com> Co-authored-by: minhtran162 <minhtran162@users.noreply.github.com> Co-authored-by: totaltube <totaltube@users.noreply.github.com> Co-authored-by: gabrielmoreira <gabrielmoreira@users.noreply.github.com> Co-authored-by: ViFigueiredo <ViFigueiredo@users.noreply.github.com> Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nicolas Lorin <androw95220@gmail.com>
* chore(release): open v3.8.10 development cycle Bump 3.8.9 → 3.8.10 across package.json, lockfile, electron, open-sse, and docs/reference/openapi.yaml; add the [3.8.10] CHANGELOG section (root + 41 i18n mirrors) as the integration target for the cycle. Entries land here as work merges into release/v3.8.10; finalized by the release flow. * fix(providers): resolve web provider alias collisions Assign unique aliases to HuggingChat, Kimi Web, and Qwen Web so they no longer shadow primary providers or trigger startup warnings. Add a unit test to enforce provider alias uniqueness and prevent future collisions. Also expand local ignore and VS Code exclude rules for agent, build, and worktree artifacts. * fix(responses): normalize image_url parts across input paths (diegosouzapw#3150) Normalize image_url parts across all Responses input paths. Integrated into release/v3.8.10. * fix(api-manager): preserve API key expiration local time (diegosouzapw#3146) Preserve API key expiration local time + clear button. Integrated into release/v3.8.10. * Strip previous_response_id for stateless Responses upstreams (diegosouzapw#3143) Strip previous_response_id for stateless Responses upstreams (auto/strip/preserve). Integrated into release/v3.8.10. * fix(opencode-plugin): map thinking cap to interleaved in model+combo (diegosouzapw#3138) Map caps.thinking to ModelV2.capabilities.interleaved for opencode-plugin. Integrated into release/v3.8.10. * fix(providers): use synced models as fallback for all providers (diegosouzapw#3148) Use synced models as authoritative local catalog for all providers (+regression test). Integrated into release/v3.8.10. * fix(qoder): bifurcate validation by token type — PAT→Cosy, regular API key→dashscope (diegosouzapw#3149) Bifurcate Qoder validation by token type (PAT→Cosy, regular→dashscope) +regression test. Integrated into release/v3.8.10. * fix(antigravity): dynamic model resolution via MITM alias table (diegosouzapw#3144) Dynamic antigravity MITM model resolution in the executor (+bug fix +regression test; DB import dropped from client-reachable config). Integrated into release/v3.8.10. * Feature/batch allow big (diegosouzapw#3128) Podman deployment options + larger upload body-size limits (+CONTAINER_HOST docs). Integrated into release/v3.8.10. * fix(fireworks): preserve fully-qualified router/model IDs (diegosouzapw#3133) (diegosouzapw#3160) Fireworks router IDs (accounts/fireworks/routers/...) were double-prefixed with accounts/fireworks/models/ → upstream 404. Add optional acceptedModelIdPrefixes to the registry entry and skip the prepend when the model already starts with an accepted prefix. Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com> * fix(llama-cpp): route to configured local baseUrl instead of OpenAI (diegosouzapw#3136) (diegosouzapw#3161) llama-cpp was missing from the local-provider group in buildUrl(), so it fell through to the OpenAI baseUrl and returned an OpenAI 401. Add the case to resolve the connection's providerSpecificData.baseUrl. Co-authored-by: tjengbudi <tjengbudi@users.noreply.github.com> * fix(t3-chat-web): parse cookies + convexSessionId from stored credential (diegosouzapw#3007) (diegosouzapw#3162) The executor read credentials.cookies/convexSessionId, but the pipeline only stores the pasted string under apiKey → t3.chat always 400'd. Parse both values from apiKey (fallback accessToken), mirroring validation.ts. Co-authored-by: minhtran162 <minhtran162@users.noreply.github.com> * fix(minimax): stop capping MiniMax-M3 / M2.7 max_tokens at 8192 (diegosouzapw#3141) (diegosouzapw#3163) MiniMax-M3 had no MODEL_SPECS entry and capitalized MiniMax-M2.7 missed its lowercase spec (case-sensitive lookup) → both fell to the 8192 default cap. Add the M3 spec (512K output), alias the capitalized ids, and make getModelSpec lookups case-insensitive. Co-authored-by: totaltube <totaltube@users.noreply.github.com> * fix(github-copilot): discover model catalog live from api.githubcopilot.com (diegosouzapw#3120, diegosouzapw#3121) (diegosouzapw#3164) The github (Copilot) provider had a static hardcoded catalog with no discovery source, so Import Models never refreshed (diegosouzapw#3120) and advertised non-entitled models that 400 on use (diegosouzapw#3121). Add a live /models fetch with fallback to the static list. Co-authored-by: gabrielmoreira <gabrielmoreira@users.noreply.github.com> * fix(combo): invalidate nested-combo cache on edits + log DATA_DIR (diegosouzapw#3147) (diegosouzapw#3165) Editing a combo did not invalidate the 10s nested-combo expansion caches (chat.ts getCombosCachedForChat + chatCore.ts getCombosCached; the exported clearCombosCache was dead code), so a removed nested target/model could be served as a phantom for up to 10s. Wire a shared monotonic combos-cache version in readCache (bumped by invalidateDbCache("combos") on every combo write); both cache layers treat a version mismatch as a miss. Also log the resolved DATA_DIR/SQLITE_FILE absolute path at DB init so the reporter's 'persists across restart + volume wipe' symptom (a multi-replica Docker volume/DATA_DIR mismatch, not a routing bug) is diagnosable from logs. Includes consolidated CHANGELOG entries for diegosouzapw#3133/diegosouzapw#3136/diegosouzapw#3007/diegosouzapw#3141/diegosouzapw#3120/diegosouzapw#3121. Co-authored-by: ViFigueiredo <ViFigueiredo@users.noreply.github.com> * fix(web-tools): parse bare JSON tool calls (diegosouzapw#3157) Parse bare JSON tool calls for deepseek-web (diegosouzapw#2820) + fuzzy tool-name matching. Integrated into release/v3.8.10. * fix(misc): minor fixes across reasoning cache, account fallback, binary manager (diegosouzapw#3177) Misc: ProviderProfile export, DeepSeek reasoning regex, binary guard. Integrated into release/v3.8.10. * fix(kiro): minor OAuth social exchange tweaks (diegosouzapw#3176) Kiro social OAuth: optional targetProvider passthrough. Integrated into release/v3.8.10. * deps: bump hono from 4.12.18 to 4.12.23 (diegosouzapw#3179) Bump hono to 4.12.23. Integrated into release/v3.8.10. * fix(providerRegistry): update kilocode format and executor (diegosouzapw#3166) kilocode: openai format + default executor (matches kilo-gateway) + registry test. Integrated into release/v3.8.10. * feat(metrics): cross-request TTFT and gap latency after tool calls (diegosouzapw#3173) Cross-request TTFT + gap-after-tool latency metrics (+test). Integrated into release/v3.8.10. * feat(dashboard): provider stats API endpoint and dashboard page (diegosouzapw#3175) Provider stats dashboard + API (SQL moved to db module per Hard Rule diegosouzapw#5, +test). Integrated into release/v3.8.10. * fix(usage): sequential+spaced OAuth quota sync, reactive force-refresh, actionable 401 (diegosouzapw#3156) Sequential+spaced OAuth quota sync, reactive force-refresh on 401, actionable 401 in UI. Integrated into release/v3.8.10. * fix(healthcheck): per-provider proactive-refresh skip list (rescue short-TTL OAuth) (diegosouzapw#3159) Per-provider proactive-refresh skip list (OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS) to rescue short-TTL OAuth. Integrated into release/v3.8.10. * feat(quota): show OAuth token expiry on provider cards (small, blue, informative) (diegosouzapw#3178) Show OAuth token expiry on provider cards (small, blue, informative). Integrated into release/v3.8.10. * fix(providers): empty refresh must not resurface just-cleared synced models (diegosouzapw#3181) Empty refresh must not resurface just-cleared synced models (fixes the release-blocking provider-models-route test). Integrated into release/v3.8.10. * chore(release): v3.8.10 — 2026-06-04 (finalize CHANGELOG) --------- Co-authored-by: Wilson <pedbookmed@gmail.com> Co-authored-by: Xiangzhe <32761048+xz-dev@users.noreply.github.com> Co-authored-by: Jan Leon <Jan.gaschler@gmail.com> Co-authored-by: M.M <mr.maatoug@gmail.com> Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com> Co-authored-by: Markus Hartung <mail@hartmark.se> Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com> Co-authored-by: tjengbudi <tjengbudi@users.noreply.github.com> Co-authored-by: minhtran162 <minhtran162@users.noreply.github.com> Co-authored-by: totaltube <totaltube@users.noreply.github.com> Co-authored-by: gabrielmoreira <gabrielmoreira@users.noreply.github.com> Co-authored-by: ViFigueiredo <ViFigueiredo@users.noreply.github.com> Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nicolas Lorin <androw95220@gmail.com>
* chore(release): open v3.8.10 development cycle Bump 3.8.9 → 3.8.10 across package.json, lockfile, electron, open-sse, and docs/reference/openapi.yaml; add the [3.8.10] CHANGELOG section (root + 41 i18n mirrors) as the integration target for the cycle. Entries land here as work merges into release/v3.8.10; finalized by the release flow. * fix(providers): resolve web provider alias collisions Assign unique aliases to HuggingChat, Kimi Web, and Qwen Web so they no longer shadow primary providers or trigger startup warnings. Add a unit test to enforce provider alias uniqueness and prevent future collisions. Also expand local ignore and VS Code exclude rules for agent, build, and worktree artifacts. * fix(responses): normalize image_url parts across input paths (diegosouzapw#3150) Normalize image_url parts across all Responses input paths. Integrated into release/v3.8.10. * fix(api-manager): preserve API key expiration local time (diegosouzapw#3146) Preserve API key expiration local time + clear button. Integrated into release/v3.8.10. * Strip previous_response_id for stateless Responses upstreams (diegosouzapw#3143) Strip previous_response_id for stateless Responses upstreams (auto/strip/preserve). Integrated into release/v3.8.10. * fix(opencode-plugin): map thinking cap to interleaved in model+combo (diegosouzapw#3138) Map caps.thinking to ModelV2.capabilities.interleaved for opencode-plugin. Integrated into release/v3.8.10. * fix(providers): use synced models as fallback for all providers (diegosouzapw#3148) Use synced models as authoritative local catalog for all providers (+regression test). Integrated into release/v3.8.10. * fix(qoder): bifurcate validation by token type — PAT→Cosy, regular API key→dashscope (diegosouzapw#3149) Bifurcate Qoder validation by token type (PAT→Cosy, regular→dashscope) +regression test. Integrated into release/v3.8.10. * fix(antigravity): dynamic model resolution via MITM alias table (diegosouzapw#3144) Dynamic antigravity MITM model resolution in the executor (+bug fix +regression test; DB import dropped from client-reachable config). Integrated into release/v3.8.10. * Feature/batch allow big (diegosouzapw#3128) Podman deployment options + larger upload body-size limits (+CONTAINER_HOST docs). Integrated into release/v3.8.10. * fix(fireworks): preserve fully-qualified router/model IDs (diegosouzapw#3133) (diegosouzapw#3160) Fireworks router IDs (accounts/fireworks/routers/...) were double-prefixed with accounts/fireworks/models/ → upstream 404. Add optional acceptedModelIdPrefixes to the registry entry and skip the prepend when the model already starts with an accepted prefix. Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com> * fix(llama-cpp): route to configured local baseUrl instead of OpenAI (diegosouzapw#3136) (diegosouzapw#3161) llama-cpp was missing from the local-provider group in buildUrl(), so it fell through to the OpenAI baseUrl and returned an OpenAI 401. Add the case to resolve the connection's providerSpecificData.baseUrl. Co-authored-by: tjengbudi <tjengbudi@users.noreply.github.com> * fix(t3-chat-web): parse cookies + convexSessionId from stored credential (diegosouzapw#3007) (diegosouzapw#3162) The executor read credentials.cookies/convexSessionId, but the pipeline only stores the pasted string under apiKey → t3.chat always 400'd. Parse both values from apiKey (fallback accessToken), mirroring validation.ts. Co-authored-by: minhtran162 <minhtran162@users.noreply.github.com> * fix(minimax): stop capping MiniMax-M3 / M2.7 max_tokens at 8192 (diegosouzapw#3141) (diegosouzapw#3163) MiniMax-M3 had no MODEL_SPECS entry and capitalized MiniMax-M2.7 missed its lowercase spec (case-sensitive lookup) → both fell to the 8192 default cap. Add the M3 spec (512K output), alias the capitalized ids, and make getModelSpec lookups case-insensitive. Co-authored-by: totaltube <totaltube@users.noreply.github.com> * fix(github-copilot): discover model catalog live from api.githubcopilot.com (diegosouzapw#3120, diegosouzapw#3121) (diegosouzapw#3164) The github (Copilot) provider had a static hardcoded catalog with no discovery source, so Import Models never refreshed (diegosouzapw#3120) and advertised non-entitled models that 400 on use (diegosouzapw#3121). Add a live /models fetch with fallback to the static list. Co-authored-by: gabrielmoreira <gabrielmoreira@users.noreply.github.com> * fix(combo): invalidate nested-combo cache on edits + log DATA_DIR (diegosouzapw#3147) (diegosouzapw#3165) Editing a combo did not invalidate the 10s nested-combo expansion caches (chat.ts getCombosCachedForChat + chatCore.ts getCombosCached; the exported clearCombosCache was dead code), so a removed nested target/model could be served as a phantom for up to 10s. Wire a shared monotonic combos-cache version in readCache (bumped by invalidateDbCache("combos") on every combo write); both cache layers treat a version mismatch as a miss. Also log the resolved DATA_DIR/SQLITE_FILE absolute path at DB init so the reporter's 'persists across restart + volume wipe' symptom (a multi-replica Docker volume/DATA_DIR mismatch, not a routing bug) is diagnosable from logs. Includes consolidated CHANGELOG entries for diegosouzapw#3133/diegosouzapw#3136/diegosouzapw#3007/diegosouzapw#3141/diegosouzapw#3120/diegosouzapw#3121. Co-authored-by: ViFigueiredo <ViFigueiredo@users.noreply.github.com> * fix(web-tools): parse bare JSON tool calls (diegosouzapw#3157) Parse bare JSON tool calls for deepseek-web (diegosouzapw#2820) + fuzzy tool-name matching. Integrated into release/v3.8.10. * fix(misc): minor fixes across reasoning cache, account fallback, binary manager (diegosouzapw#3177) Misc: ProviderProfile export, DeepSeek reasoning regex, binary guard. Integrated into release/v3.8.10. * fix(kiro): minor OAuth social exchange tweaks (diegosouzapw#3176) Kiro social OAuth: optional targetProvider passthrough. Integrated into release/v3.8.10. * deps: bump hono from 4.12.18 to 4.12.23 (diegosouzapw#3179) Bump hono to 4.12.23. Integrated into release/v3.8.10. * fix(providerRegistry): update kilocode format and executor (diegosouzapw#3166) kilocode: openai format + default executor (matches kilo-gateway) + registry test. Integrated into release/v3.8.10. * feat(metrics): cross-request TTFT and gap latency after tool calls (diegosouzapw#3173) Cross-request TTFT + gap-after-tool latency metrics (+test). Integrated into release/v3.8.10. * feat(dashboard): provider stats API endpoint and dashboard page (diegosouzapw#3175) Provider stats dashboard + API (SQL moved to db module per Hard Rule diegosouzapw#5, +test). Integrated into release/v3.8.10. * fix(usage): sequential+spaced OAuth quota sync, reactive force-refresh, actionable 401 (diegosouzapw#3156) Sequential+spaced OAuth quota sync, reactive force-refresh on 401, actionable 401 in UI. Integrated into release/v3.8.10. * fix(healthcheck): per-provider proactive-refresh skip list (rescue short-TTL OAuth) (diegosouzapw#3159) Per-provider proactive-refresh skip list (OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS) to rescue short-TTL OAuth. Integrated into release/v3.8.10. * feat(quota): show OAuth token expiry on provider cards (small, blue, informative) (diegosouzapw#3178) Show OAuth token expiry on provider cards (small, blue, informative). Integrated into release/v3.8.10. * fix(providers): empty refresh must not resurface just-cleared synced models (diegosouzapw#3181) Empty refresh must not resurface just-cleared synced models (fixes the release-blocking provider-models-route test). Integrated into release/v3.8.10. * chore(release): v3.8.10 — 2026-06-04 (finalize CHANGELOG) --------- Co-authored-by: Wilson <pedbookmed@gmail.com> Co-authored-by: Xiangzhe <32761048+xz-dev@users.noreply.github.com> Co-authored-by: Jan Leon <Jan.gaschler@gmail.com> Co-authored-by: M.M <mr.maatoug@gmail.com> Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com> Co-authored-by: Markus Hartung <mail@hartmark.se> Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com> Co-authored-by: tjengbudi <tjengbudi@users.noreply.github.com> Co-authored-by: minhtran162 <minhtran162@users.noreply.github.com> Co-authored-by: totaltube <totaltube@users.noreply.github.com> Co-authored-by: gabrielmoreira <gabrielmoreira@users.noreply.github.com> Co-authored-by: ViFigueiredo <ViFigueiredo@users.noreply.github.com> Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nicolas Lorin <androw95220@gmail.com>
…ort-TTL OAuth) (diegosouzapw#3159) Per-provider proactive-refresh skip list (OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS) to rescue short-TTL OAuth. Integrated into release/v3.8.10.
* chore(release): open v3.8.10 development cycle Bump 3.8.9 → 3.8.10 across package.json, lockfile, electron, open-sse, and docs/reference/openapi.yaml; add the [3.8.10] CHANGELOG section (root + 41 i18n mirrors) as the integration target for the cycle. Entries land here as work merges into release/v3.8.10; finalized by the release flow. * fix(providers): resolve web provider alias collisions Assign unique aliases to HuggingChat, Kimi Web, and Qwen Web so they no longer shadow primary providers or trigger startup warnings. Add a unit test to enforce provider alias uniqueness and prevent future collisions. Also expand local ignore and VS Code exclude rules for agent, build, and worktree artifacts. * fix(responses): normalize image_url parts across input paths (diegosouzapw#3150) Normalize image_url parts across all Responses input paths. Integrated into release/v3.8.10. * fix(api-manager): preserve API key expiration local time (diegosouzapw#3146) Preserve API key expiration local time + clear button. Integrated into release/v3.8.10. * Strip previous_response_id for stateless Responses upstreams (diegosouzapw#3143) Strip previous_response_id for stateless Responses upstreams (auto/strip/preserve). Integrated into release/v3.8.10. * fix(opencode-plugin): map thinking cap to interleaved in model+combo (diegosouzapw#3138) Map caps.thinking to ModelV2.capabilities.interleaved for opencode-plugin. Integrated into release/v3.8.10. * fix(providers): use synced models as fallback for all providers (diegosouzapw#3148) Use synced models as authoritative local catalog for all providers (+regression test). Integrated into release/v3.8.10. * fix(qoder): bifurcate validation by token type — PAT→Cosy, regular API key→dashscope (diegosouzapw#3149) Bifurcate Qoder validation by token type (PAT→Cosy, regular→dashscope) +regression test. Integrated into release/v3.8.10. * fix(antigravity): dynamic model resolution via MITM alias table (diegosouzapw#3144) Dynamic antigravity MITM model resolution in the executor (+bug fix +regression test; DB import dropped from client-reachable config). Integrated into release/v3.8.10. * Feature/batch allow big (diegosouzapw#3128) Podman deployment options + larger upload body-size limits (+CONTAINER_HOST docs). Integrated into release/v3.8.10. * fix(fireworks): preserve fully-qualified router/model IDs (diegosouzapw#3133) (diegosouzapw#3160) Fireworks router IDs (accounts/fireworks/routers/...) were double-prefixed with accounts/fireworks/models/ → upstream 404. Add optional acceptedModelIdPrefixes to the registry entry and skip the prepend when the model already starts with an accepted prefix. Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com> * fix(llama-cpp): route to configured local baseUrl instead of OpenAI (diegosouzapw#3136) (diegosouzapw#3161) llama-cpp was missing from the local-provider group in buildUrl(), so it fell through to the OpenAI baseUrl and returned an OpenAI 401. Add the case to resolve the connection's providerSpecificData.baseUrl. Co-authored-by: tjengbudi <tjengbudi@users.noreply.github.com> * fix(t3-chat-web): parse cookies + convexSessionId from stored credential (diegosouzapw#3007) (diegosouzapw#3162) The executor read credentials.cookies/convexSessionId, but the pipeline only stores the pasted string under apiKey → t3.chat always 400'd. Parse both values from apiKey (fallback accessToken), mirroring validation.ts. Co-authored-by: minhtran162 <minhtran162@users.noreply.github.com> * fix(minimax): stop capping MiniMax-M3 / M2.7 max_tokens at 8192 (diegosouzapw#3141) (diegosouzapw#3163) MiniMax-M3 had no MODEL_SPECS entry and capitalized MiniMax-M2.7 missed its lowercase spec (case-sensitive lookup) → both fell to the 8192 default cap. Add the M3 spec (512K output), alias the capitalized ids, and make getModelSpec lookups case-insensitive. Co-authored-by: totaltube <totaltube@users.noreply.github.com> * fix(github-copilot): discover model catalog live from api.githubcopilot.com (diegosouzapw#3120, diegosouzapw#3121) (diegosouzapw#3164) The github (Copilot) provider had a static hardcoded catalog with no discovery source, so Import Models never refreshed (diegosouzapw#3120) and advertised non-entitled models that 400 on use (diegosouzapw#3121). Add a live /models fetch with fallback to the static list. Co-authored-by: gabrielmoreira <gabrielmoreira@users.noreply.github.com> * fix(combo): invalidate nested-combo cache on edits + log DATA_DIR (diegosouzapw#3147) (diegosouzapw#3165) Editing a combo did not invalidate the 10s nested-combo expansion caches (chat.ts getCombosCachedForChat + chatCore.ts getCombosCached; the exported clearCombosCache was dead code), so a removed nested target/model could be served as a phantom for up to 10s. Wire a shared monotonic combos-cache version in readCache (bumped by invalidateDbCache("combos") on every combo write); both cache layers treat a version mismatch as a miss. Also log the resolved DATA_DIR/SQLITE_FILE absolute path at DB init so the reporter's 'persists across restart + volume wipe' symptom (a multi-replica Docker volume/DATA_DIR mismatch, not a routing bug) is diagnosable from logs. Includes consolidated CHANGELOG entries for diegosouzapw#3133/diegosouzapw#3136/diegosouzapw#3007/diegosouzapw#3141/diegosouzapw#3120/diegosouzapw#3121. Co-authored-by: ViFigueiredo <ViFigueiredo@users.noreply.github.com> * fix(web-tools): parse bare JSON tool calls (diegosouzapw#3157) Parse bare JSON tool calls for deepseek-web (diegosouzapw#2820) + fuzzy tool-name matching. Integrated into release/v3.8.10. * fix(misc): minor fixes across reasoning cache, account fallback, binary manager (diegosouzapw#3177) Misc: ProviderProfile export, DeepSeek reasoning regex, binary guard. Integrated into release/v3.8.10. * fix(kiro): minor OAuth social exchange tweaks (diegosouzapw#3176) Kiro social OAuth: optional targetProvider passthrough. Integrated into release/v3.8.10. * deps: bump hono from 4.12.18 to 4.12.23 (diegosouzapw#3179) Bump hono to 4.12.23. Integrated into release/v3.8.10. * fix(providerRegistry): update kilocode format and executor (diegosouzapw#3166) kilocode: openai format + default executor (matches kilo-gateway) + registry test. Integrated into release/v3.8.10. * feat(metrics): cross-request TTFT and gap latency after tool calls (diegosouzapw#3173) Cross-request TTFT + gap-after-tool latency metrics (+test). Integrated into release/v3.8.10. * feat(dashboard): provider stats API endpoint and dashboard page (diegosouzapw#3175) Provider stats dashboard + API (SQL moved to db module per Hard Rule diegosouzapw#5, +test). Integrated into release/v3.8.10. * fix(usage): sequential+spaced OAuth quota sync, reactive force-refresh, actionable 401 (diegosouzapw#3156) Sequential+spaced OAuth quota sync, reactive force-refresh on 401, actionable 401 in UI. Integrated into release/v3.8.10. * fix(healthcheck): per-provider proactive-refresh skip list (rescue short-TTL OAuth) (diegosouzapw#3159) Per-provider proactive-refresh skip list (OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS) to rescue short-TTL OAuth. Integrated into release/v3.8.10. * feat(quota): show OAuth token expiry on provider cards (small, blue, informative) (diegosouzapw#3178) Show OAuth token expiry on provider cards (small, blue, informative). Integrated into release/v3.8.10. * fix(providers): empty refresh must not resurface just-cleared synced models (diegosouzapw#3181) Empty refresh must not resurface just-cleared synced models (fixes the release-blocking provider-models-route test). Integrated into release/v3.8.10. * chore(release): v3.8.10 — 2026-06-04 (finalize CHANGELOG) --------- Co-authored-by: Wilson <pedbookmed@gmail.com> Co-authored-by: Xiangzhe <32761048+xz-dev@users.noreply.github.com> Co-authored-by: Jan Leon <Jan.gaschler@gmail.com> Co-authored-by: M.M <mr.maatoug@gmail.com> Co-authored-by: Hernan Javier Ardila Sanchez <hjasgr@gmail.com> Co-authored-by: Markus Hartung <mail@hartmark.se> Co-authored-by: KooshaPari <KooshaPari@users.noreply.github.com> Co-authored-by: tjengbudi <tjengbudi@users.noreply.github.com> Co-authored-by: minhtran162 <minhtran162@users.noreply.github.com> Co-authored-by: totaltube <totaltube@users.noreply.github.com> Co-authored-by: gabrielmoreira <gabrielmoreira@users.noreply.github.com> Co-authored-by: ViFigueiredo <ViFigueiredo@users.noreply.github.com> Co-authored-by: PizzaV <103120356+pizzav-xyz@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nicolas Lorin <androw95220@gmail.com>
Why
Investigated the stuck "Token Expired" Kimi-coding account on the
192.168.0.15box (while .16/.17/.18 showed live ~4–11 min countdowns). Root cause:.15hasOMNIROUTE_DISABLE_TOKEN_HEALTHCHECK=trueset — the proactive token-refresh sweep is fully off (a leftover guard from the fix(sse): stop Codex multi-account family-revocation cascade on quota-sync #3019 Codex Auth0-family cascade, where the background sweep was the trigger for "adding account B invalidates account A").token_expires_at~2.5 days stale,last_health_check_at=null,test_status=active→ UI badge "Token Expired".What
Add
OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS(comma-separated, case-insensitive).checkConnectionskips only the listed providers from the proactive sweep. An operator can setcodex,openaito keep the rotating-cascade family reactive-only (preserving the #3019 protection) while Kimi/Claude/etc. resume proactive refresh — so.15can drop the blunt full-disable.Fix on .15 (operational, after merge+deploy)
Replace
OMNIROUTE_DISABLE_TOKEN_HEALTHCHECK=truewithOMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS=codex,openai(andpm2 restart --update-env). Codex stays reactive-only; Kimi gets proactively refreshed again.Tests (TDD — red→green verified)
token-health-check→ "skips providers listed in OMNIROUTE_HEALTHCHECK_SKIP_PROVIDERS": a listed provider triggers no proactive refresh; the same due connection is refreshed once removed from the list (control proves the skip, not token freshness, gated it). Fails on the pre-fix sweep.typecheck:coreclean;env-doc-syncupdated (.env.example+ENVIRONMENT.md); fulltoken-health-checksuite 7/7.Note on the refresh-timing question
The 5-min proactive lead (
TOKEN_EXPIRY_BUFFER) is adequate for Kimi's TTL when the sweep runs — the timing wasn't the problem; the disabled sweep was. This PR restores proactive coverage for short-TTL providers without re-exposing Codex.