fix(auth): exclude local CLI providers from tokenHealthCheck expiration (Fixes #8407) (#8426)

* fix(providers): prevent health sweep from expiring devin-cli local credentials

* fix(auth): drop devin-cli from supportsTokenRefresh explicit set (#8407)

Root cause: listing "devin-cli" as refresh-capable made tokenHealthCheck
force-expire local CLI connections that never have a refresh token.

Remove it from the explicit set (keep windsurf) and drop the health-check
provider hardcode — the existing supportsTokenRefresh=false guard is enough.
This commit is contained in:
MumuTW
2026-07-25 13:51:35 +08:00
committed by GitHub
parent 73c5e27379
commit 4528fc455e
3 changed files with 74 additions and 1 deletions

View File

@@ -472,7 +472,11 @@ export function supportsTokenRefresh(provider) {
"cline",
"kimi-coding",
"windsurf",
"devin-cli",
// #8407: do NOT list "devin-cli" here. It is import-token / local-CLI owned
// (`devin auth login`); connections never carry a refresh token. Leaving it
// in this set made tokenHealthCheck treat it as refresh-capable and force
// testStatus="expired" / errorCode="no_refresh_token". Keep it out of the
// explicit set (same idea as not listing non-refresh local-CLI providers).
"gitlab-duo",
"codebuddy-cn",
]);

View File

@@ -513,6 +513,8 @@ export async function checkConnection(conn) {
// cosmetic "Token Expired". Surface reality as a terminal "expired" status instead.
// Guard tightly so we do NOT clobber:
// - providers that simply don't use refresh tokens (supportsTokenRefresh=false)
// (see #8407: local-CLI import-token providers like devin-cli must not be
// listed in supportsTokenRefresh's explicit set)
// - connections already in a terminal/specific state (expired/banned/credits_exhausted)
// - transient cooldown state (unavailable) owned by the request path
const refreshCapableNeedsReauth =

View File

@@ -0,0 +1,67 @@
// #8407: devin-cli must not be treated as refresh-capable, so the health sweep
// never force-expires local CLI connections that legitimately have no refresh token.
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
process.env.NODE_ENV = "test";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-hc-devin-cli-8407-"));
process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const providersDb = await import("../../src/lib/db/providers.ts");
const tokenHealthCheck = await import("../../src/lib/tokenHealthCheck.ts");
const { supportsTokenRefresh } = await import("../../open-sse/services/tokenRefresh.ts");
async function resetStorage() {
core.resetDbInstance();
if (fs.existsSync(TEST_DATA_DIR)) {
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
}
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
}
function getCreatedConnectionId(connection: { id?: unknown }): string {
assert.equal(typeof connection.id, "string");
return connection.id as string;
}
test.after(async () => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
test("supportsTokenRefresh excludes devin-cli (#8407)", () => {
// Root fix: drop "devin-cli" from the explicit set so the health sweep's
// supportsTokenRefresh=false guard applies (same idea as not listing a
// non-refresh local-CLI provider). windsurf stays refresh-capable.
assert.equal(
supportsTokenRefresh("devin-cli"),
false,
"devin-cli is local import-token / CLI-owned — not refresh-capable"
);
assert.equal(supportsTokenRefresh("windsurf"), true);
});
test("checkConnection leaves a devin-cli connection with no refresh token untouched (#8407)", async () => {
await resetStorage();
const connection = await providersDb.createProviderConnection({
provider: "devin-cli",
authType: "oauth",
name: "Devin CLI Local Account",
accessToken: "local-cli-access-token",
refreshToken: null,
testStatus: "active",
isActive: true,
});
await tokenHealthCheck.checkConnection(connection);
const updated = await providersDb.getProviderConnectionById(getCreatedConnectionId(connection));
assert.equal(updated?.testStatus, "active", "devin-cli testStatus must remain active");
assert.notEqual(updated?.errorCode, "no_refresh_token", "devin-cli must not be marked no_refresh_token");
});