diff --git a/src/cli/daemon-cli/gateway-token-drift.test.ts b/src/cli/daemon-cli/gateway-token-drift.test.ts new file mode 100644 index 000000000..58f4b706e --- /dev/null +++ b/src/cli/daemon-cli/gateway-token-drift.test.ts @@ -0,0 +1,23 @@ +import { describe, expect, it } from "vitest"; +import type { OpenClawConfig } from "../../config/config.js"; +import { resolveGatewayTokenForDriftCheck } from "./gateway-token-drift.js"; + +describe("resolveGatewayTokenForDriftCheck", () => { + it("prefers persisted config token over shell env", () => { + const token = resolveGatewayTokenForDriftCheck({ + cfg: { + gateway: { + mode: "local", + auth: { + token: "config-token", + }, + }, + } as OpenClawConfig, + env: { + OPENCLAW_GATEWAY_TOKEN: "env-token", + } as NodeJS.ProcessEnv, + }); + + expect(token).toBe("config-token"); + }); +}); diff --git a/src/cli/daemon-cli/gateway-token-drift.ts b/src/cli/daemon-cli/gateway-token-drift.ts new file mode 100644 index 000000000..e4421b32a --- /dev/null +++ b/src/cli/daemon-cli/gateway-token-drift.ts @@ -0,0 +1,16 @@ +import type { OpenClawConfig } from "../../config/config.js"; +import { resolveGatewayCredentialsFromConfig } from "../../gateway/credentials.js"; + +export function resolveGatewayTokenForDriftCheck(params: { + cfg: OpenClawConfig; + env?: NodeJS.ProcessEnv; +}) { + return resolveGatewayCredentialsFromConfig({ + cfg: params.cfg, + env: params.env, + modeOverride: "local", + // Drift checks should compare the persisted gateway token against the + // service token, not let an exported shell env mask config drift. + localTokenPrecedence: "config-first", + }).token; +} diff --git a/src/cli/daemon-cli/lifecycle-core.ts b/src/cli/daemon-cli/lifecycle-core.ts index 5e6daf188..db0b2182e 100644 --- a/src/cli/daemon-cli/lifecycle-core.ts +++ b/src/cli/daemon-cli/lifecycle-core.ts @@ -5,12 +5,10 @@ import { checkTokenDrift } from "../../daemon/service-audit.js"; import type { GatewayService } from "../../daemon/service.js"; import { renderSystemdUnavailableHints } from "../../daemon/systemd-hints.js"; import { isSystemdUserServiceAvailable } from "../../daemon/systemd.js"; -import { - isGatewaySecretRefUnavailableError, - resolveGatewayCredentialsFromConfig, -} from "../../gateway/credentials.js"; +import { isGatewaySecretRefUnavailableError } from "../../gateway/credentials.js"; import { isWSL } from "../../infra/wsl.js"; import { defaultRuntime } from "../../runtime.js"; +import { resolveGatewayTokenForDriftCheck } from "./gateway-token-drift.js"; import { buildDaemonServiceSnapshot, createNullWriter, @@ -284,14 +282,7 @@ export async function runServiceRestart(params: { const command = await params.service.readCommand(process.env); const serviceToken = command?.environment?.OPENCLAW_GATEWAY_TOKEN; const cfg = loadConfig(); - const configToken = resolveGatewayCredentialsFromConfig({ - cfg, - env: process.env, - modeOverride: "local", - // Drift checks should compare the persisted gateway token against the - // service token, not let an exported shell env mask config drift. - localTokenPrecedence: "config-first", - }).token; + const configToken = resolveGatewayTokenForDriftCheck({ cfg, env: process.env }); const driftIssue = checkTokenDrift({ serviceToken, configToken }); if (driftIssue) { const warning = driftIssue.detail