fix(gateway): land #28428 from @l0cka

Landed from contributor PR #28428 by @l0cka.

Co-authored-by: Daniel Alkurdi <danielalkurdi@gmail.com>
This commit is contained in:
Peter Steinberger
2026-03-07 22:49:50 +00:00
parent e83094e63f
commit 265367d99b
26 changed files with 289 additions and 165 deletions

View File

@@ -116,7 +116,7 @@ describe("runDaemonInstall integration", () => {
expect(joined).toContain("MISSING_GATEWAY_TOKEN");
});
it("auto-mints token when no source exists and persists the same token used for install env", async () => {
it("auto-mints token when no source exists without embedding it into service env", async () => {
await fs.writeFile(
configPath,
JSON.stringify(
@@ -143,6 +143,6 @@ describe("runDaemonInstall integration", () => {
expect((persistedToken ?? "").length).toBeGreaterThan(0);
const installEnv = serviceMock.install.mock.calls[0]?.[0]?.environment;
expect(installEnv?.OPENCLAW_GATEWAY_TOKEN).toBe(persistedToken);
expect(installEnv?.OPENCLAW_GATEWAY_TOKEN).toBeUndefined();
});
});

View File

@@ -197,11 +197,8 @@ describe("runDaemonInstall", () => {
await runDaemonInstall({ json: true });
expect(actionState.failed).toEqual([]);
expect(buildGatewayInstallPlanMock).toHaveBeenCalledWith(
expect.objectContaining({
token: undefined,
}),
);
expect(buildGatewayInstallPlanMock).toHaveBeenCalledTimes(1);
expect("token" in buildGatewayInstallPlanMock.mock.calls[0][0]).toBe(false);
expect(writeConfigFileMock).not.toHaveBeenCalled();
expect(
actionState.warnings.some((warning) =>
@@ -225,11 +222,8 @@ describe("runDaemonInstall", () => {
expect(actionState.failed).toEqual([]);
expect(resolveSecretRefValuesMock).toHaveBeenCalledTimes(1);
expect(buildGatewayInstallPlanMock).toHaveBeenCalledWith(
expect.objectContaining({
token: undefined,
}),
);
expect(buildGatewayInstallPlanMock).toHaveBeenCalledTimes(1);
expect("token" in buildGatewayInstallPlanMock.mock.calls[0][0]).toBe(false);
});
it("auto-mints and persists token when no source exists", async () => {
@@ -249,8 +243,9 @@ describe("runDaemonInstall", () => {
};
expect(writtenConfig.gateway?.auth?.token).toBe("minted-token");
expect(buildGatewayInstallPlanMock).toHaveBeenCalledWith(
expect.objectContaining({ token: "minted-token", port: 18789 }),
expect.objectContaining({ port: 18789 }),
);
expect("token" in buildGatewayInstallPlanMock.mock.calls[0][0]).toBe(false);
expect(installDaemonServiceAndEmitMock).toHaveBeenCalledTimes(1);
expect(actionState.warnings.some((warning) => warning.includes("Auto-generated"))).toBe(true);
});

View File

@@ -91,7 +91,6 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) {
const { programArguments, workingDirectory, environment } = await buildGatewayInstallPlan({
env: process.env,
port,
token: tokenResolution.token,
runtime: runtimeRaw,
warn: (message) => {
if (json) {

View File

@@ -83,7 +83,7 @@ describe("runServiceRestart token drift", () => {
expect(payload.warnings?.[0]).toContain("gateway install --force");
});
it("uses env-first token precedence when checking drift", async () => {
it("uses gateway.auth.token when checking drift", async () => {
loadConfig.mockReturnValue({
gateway: {
auth: {
@@ -106,7 +106,7 @@ describe("runServiceRestart token drift", () => {
const jsonLine = runtimeLogs.find((line) => line.trim().startsWith("{"));
const payload = JSON.parse(jsonLine ?? "{}") as { warnings?: string[] };
expect(payload.warnings).toBeUndefined();
expect(payload.warnings?.[0]).toContain("gateway install --force");
});
it("skips drift warning when disabled", async () => {

View File

@@ -5,10 +5,7 @@ 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 {
@@ -284,11 +281,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",
}).token;
const configToken = cfg.gateway?.auth?.token?.trim() || undefined;
const driftIssue = checkTokenDrift({ serviceToken, configToken });
if (driftIssue) {
const warning = driftIssue.detail