惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hugging Face - Blog
Hugging Face - Blog
Google DeepMind News
Google DeepMind News
云风的 BLOG
云风的 BLOG
WordPress大学
WordPress大学
Vercel News
Vercel News
Apple Machine Learning Research
Apple Machine Learning Research
T
Tailwind CSS Blog
I
InfoQ
小众软件
小众软件
Recent Announcements
Recent Announcements
博客园 - 【当耐特】
The GitHub Blog
The GitHub Blog
大猫的无限游戏
大猫的无限游戏
美团技术团队
T
The Blog of Author Tim Ferriss
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
酷 壳 – CoolShell
酷 壳 – CoolShell
MongoDB | Blog
MongoDB | Blog
V
V2EX
J
Java Code Geeks
有赞技术团队
有赞技术团队
博客园 - 聂微东
B
Blog RSS Feed
博客园 - 司徒正美

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(deadcode): move restart sentinels to sqlite · opencla...
vincentkoc · 2026-06-21 · via Recent Commits to openclaw:main
Original file line numberDiff line numberDiff line change

@@ -3,10 +3,11 @@ import fs from "node:fs/promises";

33

import os from "node:os";

44

import path from "node:path";

55

import { beforeEach, describe, expect, it, vi } from "vitest";

6+

import { normalizeConfigPatchReplacePath } from "../config/patch-replace-paths.js";

67

import { GatewayClientRequestError } from "../gateway/client.js";

8+

import { readRestartSentinel } from "../infra/restart-sentinel.js";

79

import { testing as restartTesting } from "../infra/restart.js";

810

import { withEnvAsync } from "../test-utils/env.js";

9-

import { normalizeConfigPatchReplacePath } from "../config/patch-replace-paths.js";

1011

import { createGatewayTool } from "./tools/gateway-tool.js";

1112

import { callGatewayTool } from "./tools/gateway.js";

1213

@@ -332,13 +333,9 @@ describe("gateway tool", () => {

332333

});

333334

expect(restartSignalKillCalls()).toHaveLength(0);

334335
335-

const sentinelPath = path.join(stateDir, "restart-sentinel.json");

336-

const raw = await fs.readFile(sentinelPath, "utf-8");

337-

const parsed = JSON.parse(raw) as {

338-

payload?: { kind?: string; doctorHint?: string | null };

339-

};

340-

expect(parsed.payload?.kind).toBe("restart");

341-

expect(parsed.payload?.doctorHint).toBe(

336+

const sentinel = await readRestartSentinel();

337+

expect(sentinel?.payload.kind).toBe("restart");

338+

expect(sentinel?.payload.doctorHint).toBe(

342339

"Recommended follow-up: run openclaw --profile isolated doctor --non-interactive in a terminal or approvals-capable OpenClaw surface.",

343340

);

344341

},

@@ -507,15 +504,13 @@ describe("gateway tool", () => {

507504

it("distinguishes explicit terminal array consent from indexed consent", () => {

508505

expect(normalizeConfigPatchReplacePath("bindings[]")).toBe("bindings");

509506

expect(normalizeConfigPatchReplacePath("bindings[0]")).toBe("bindings[0]");

510-

expect(normalizeConfigPatchReplacePath("agents.list[0].skills")).toBe(

511-

"agents.list[].skills",

512-

);

507+

expect(normalizeConfigPatchReplacePath("agents.list[0].skills")).toBe("agents.list[].skills");

513508

expect(normalizeConfigPatchReplacePath(normalizeConfigPatchReplacePath("bindings[]"))).toBe(

514509

"bindings",

515510

);

516-

expect(

517-

normalizeConfigPatchReplacePath(normalizeConfigPatchReplacePath("bindings[0]")),

518-

).toBe("bindings[0]");

511+

expect(normalizeConfigPatchReplacePath(normalizeConfigPatchReplacePath("bindings[0]"))).toBe(

512+

"bindings[0]",

513+

);

519514

});

520515
521516

it("rejects config.patch when it changes safe bin approval paths", async () => {

Original file line numberDiff line numberDiff line change

@@ -12,7 +12,7 @@ const {

1212

formatDoctorNonInteractiveHintMock,

1313

isRestartEnabledMock,

1414

callGatewayToolMock,

15-

removeRestartSentinelFileMock,

15+

clearRestartSentinelMock,

1616

scheduleGatewaySigusr1RestartMock,

1717

writeRestartSentinelMock,

1818

} = vi.hoisted(() => ({

@@ -30,8 +30,8 @@ const {

3030

() =>

3131

"Recommended follow-up: run openclaw doctor --non-interactive in a terminal or approvals-capable OpenClaw surface.",

3232

),

33-

writeRestartSentinelMock: vi.fn(async (_payload: RestartSentinelPayload) => "/tmp/restart"),

34-

removeRestartSentinelFileMock: vi.fn(async (_path: string | null | undefined) => undefined),

33+

writeRestartSentinelMock: vi.fn(async (_payload: RestartSentinelPayload) => undefined),

34+

clearRestartSentinelMock: vi.fn(async () => undefined),

3535

scheduleGatewaySigusr1RestartMock: vi.fn((_opts?: ScheduleGatewayRestartArgs) => ({

3636

ok: true,

3737

pid: 123,

@@ -59,7 +59,7 @@ vi.mock("../../infra/restart-sentinel.js", async () => {

5959

return {

6060

...actual,

6161

formatDoctorNonInteractiveHint: formatDoctorNonInteractiveHintMock,

62-

removeRestartSentinelFile: removeRestartSentinelFileMock,

62+

clearRestartSentinel: clearRestartSentinelMock,

6363

writeRestartSentinel: writeRestartSentinelMock,

6464

};

6565

});

@@ -115,8 +115,8 @@ describe("gateway tool restart continuation", () => {

115115

"Recommended follow-up: run openclaw doctor --non-interactive in a terminal or approvals-capable OpenClaw surface.",

116116

);

117117

writeRestartSentinelMock.mockReset();

118-

writeRestartSentinelMock.mockResolvedValue("/tmp/restart");

119-

removeRestartSentinelFileMock.mockClear();

118+

writeRestartSentinelMock.mockResolvedValue(undefined);

119+

clearRestartSentinelMock.mockClear();

120120

scheduleGatewaySigusr1RestartMock.mockReset();

121121

scheduleGatewaySigusr1RestartMock.mockReturnValue({

122122

ok: true,

@@ -354,7 +354,7 @@ describe("gateway tool restart continuation", () => {

354354

await scheduledArgs.emitHooks?.beforeEmit?.();

355355

await scheduledArgs.emitHooks?.afterEmitRejected?.();

356356
357-

expect(removeRestartSentinelFileMock).toHaveBeenCalledWith("/tmp/restart");

357+

expect(clearRestartSentinelMock).toHaveBeenCalledOnce();

358358

});

359359
360360

it("uses the runtime session for update.run continuation routing (#86742)", async () => {

Original file line numberDiff line numberDiff line change

@@ -19,8 +19,8 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js";

1919

import { GatewayClientRequestError } from "../../gateway/client.js";

2020

import {

2121

buildRestartSuccessContinuation,

22+

clearRestartSentinel,

2223

formatDoctorNonInteractiveHint,

23-

removeRestartSentinelFile,

2424

type RestartSentinelPayload,

2525

writeRestartSentinel,

2626

} from "../../infra/restart-sentinel.js";

@@ -493,7 +493,7 @@ export function createGatewayTool(opts?: {

493493

log.info(

494494

`gateway tool: restart requested (delayMs=${delayMs ?? "default"}, reason=${reason ?? "none"})`,

495495

);

496-

let sentinelPath: string | null = null;

496+

let sentinelWritten = false;

497497

const scheduled = scheduleGatewaySigusr1Restart({

498498

delayMs,

499499

reason,

@@ -502,10 +502,13 @@ export function createGatewayTool(opts?: {

502502

sessionKey,

503503

emitHooks: {

504504

beforeEmit: async () => {

505-

sentinelPath = await writeRestartSentinel(payload);

505+

await writeRestartSentinel(payload);

506+

sentinelWritten = true;

506507

},

507508

afterEmitRejected: async () => {

508-

await removeRestartSentinelFile(sentinelPath);

509+

if (sentinelWritten) {

510+

await clearRestartSentinel();

511+

}

509512

},

510513

},

511514

});

Original file line numberDiff line numberDiff line change

@@ -7,7 +7,7 @@ import type { HandleCommandsParams } from "./commands-types.js";

77

type ScheduleGatewayRestartArgs = Parameters<typeof scheduleGatewaySigusr1Restart>[0];

88
99

const mocks = vi.hoisted(() => ({

10-

unlink: vi.fn(async (_path: string) => undefined),

10+

clearRestartSentinel: vi.fn(async () => undefined),

1111

isRestartEnabled: vi.fn(() => true),

1212

extractDeliveryInfo: vi.fn(() => ({

1313

deliveryContext: {

@@ -21,20 +21,13 @@ const mocks = vi.hoisted(() => ({

2121

() =>

2222

"Recommended follow-up: run openclaw doctor --non-interactive in a terminal or approvals-capable OpenClaw surface.",

2323

),

24-

writeRestartSentinel: vi.fn(async (_payload: RestartSentinelPayload) => "/tmp/sentinel.json"),

24+

writeRestartSentinel: vi.fn(async (_payload: RestartSentinelPayload) => undefined),

2525

scheduleGatewaySigusr1Restart: vi.fn((_opts?: ScheduleGatewayRestartArgs) => ({

2626

scheduled: true,

2727

})),

2828

triggerOpenClawRestart: vi.fn(() => ({ ok: true, method: "launchctl" })),

2929

}));

3030
31-

vi.mock("node:fs/promises", () => ({

32-

default: {

33-

unlink: mocks.unlink,

34-

},

35-

unlink: mocks.unlink,

36-

}));

37-
3831

vi.mock("../../config/commands.flags.js", () => ({

3932

isRestartEnabled: mocks.isRestartEnabled,

4033

}));

@@ -67,6 +60,7 @@ vi.mock("../../infra/restart-sentinel.js", async () => {

6760

);

6861

return {

6962

...actual,

63+

clearRestartSentinel: mocks.clearRestartSentinel,

7064

formatDoctorNonInteractiveHint: mocks.formatDoctorNonInteractiveHint,

7165

writeRestartSentinel: mocks.writeRestartSentinel,

7266

};

@@ -119,7 +113,7 @@ describe("handleRestartCommand", () => {

119113

beforeEach(() => {

120114

mocks.isRestartEnabled.mockReset();

121115

mocks.isRestartEnabled.mockReturnValue(true);

122-

mocks.unlink.mockClear();

116+

mocks.clearRestartSentinel.mockClear();

123117

mocks.extractDeliveryInfo.mockClear();

124118

mocks.formatDoctorNonInteractiveHint.mockClear();

125119

mocks.writeRestartSentinel.mockClear();

@@ -218,7 +212,7 @@ describe("handleRestartCommand", () => {

218212

expect(mocks.triggerOpenClawRestart).not.toHaveBeenCalled();

219213

});

220214
221-

it("removes the success sentinel when fallback restart fails", async () => {

215+

it("clears the success sentinel when fallback restart fails", async () => {

222216

mocks.triggerOpenClawRestart.mockReturnValueOnce({

223217

ok: false,

224218

method: "launchctl",

@@ -227,6 +221,6 @@ describe("handleRestartCommand", () => {

227221

const result = await handleRestartCommand(restartCommandParams(), true);

228222
229223

expect(result?.reply?.text).toContain("Restart failed");

230-

expect(mocks.unlink).toHaveBeenCalledWith("/tmp/sentinel.json");

224+

expect(mocks.clearRestartSentinel).toHaveBeenCalledOnce();

231225

});

232226

});

Original file line numberDiff line numberDiff line change

@@ -21,8 +21,8 @@ import { getSessionBindingService } from "../../infra/outbound/session-binding-s

2121

import type { SessionBindingRecord } from "../../infra/outbound/session-binding-service.js";

2222

import {

2323

buildRestartSuccessContinuation,

24+

clearRestartSentinel,

2425

formatDoctorNonInteractiveHint,

25-

removeRestartSentinelFile,

2626

type RestartSentinelPayload,

2727

writeRestartSentinel,

2828

} from "../../infra/restart-sentinel.js";

@@ -703,7 +703,7 @@ export const handleRestartCommand: CommandHandler = async (params, allowTextComm

703703

const hasSigusr1Listener = process.listenerCount("SIGUSR1") > 0;

704704

const sentinelPayload = buildRestartCommandSentinel(params);

705705

if (hasSigusr1Listener) {

706-

let sentinelPath: string | null = null;

706+

let sentinelWritten = false;

707707

scheduleGatewaySigusr1Restart({

708708

reason: "/restart",

709709

// Sibling session-routing guard: /restart writes a session-scoped sentinel

@@ -713,10 +713,13 @@ export const handleRestartCommand: CommandHandler = async (params, allowTextComm

713713

emitHooks: sentinelPayload

714714

? {

715715

beforeEmit: async () => {

716-

sentinelPath = await writeRestartSentinel(sentinelPayload);

716+

await writeRestartSentinel(sentinelPayload);

717+

sentinelWritten = true;

717718

},

718719

afterEmitRejected: async () => {

719-

await removeRestartSentinelFile(sentinelPath);

720+

if (sentinelWritten) {

721+

await clearRestartSentinel();

722+

}

720723

},

721724

}

722725

: undefined,

@@ -728,10 +731,11 @@ export const handleRestartCommand: CommandHandler = async (params, allowTextComm

728731

},

729732

};

730733

}

731-

let sentinelPath: string | null = null;

734+

let sentinelWritten = false;

732735

try {

733736

if (sentinelPayload) {

734-

sentinelPath = await writeRestartSentinel(sentinelPayload);

737+

await writeRestartSentinel(sentinelPayload);

738+

sentinelWritten = true;

735739

}

736740

} catch (err) {

737741

logVerbose(`failed to write /restart sentinel: ${String(err)}`);

@@ -744,7 +748,9 @@ export const handleRestartCommand: CommandHandler = async (params, allowTextComm

744748

}

745749

const restartMethod = triggerOpenClawRestart();

746750

if (!restartMethod.ok) {

747-

await removeRestartSentinelFile(sentinelPath);

751+

if (sentinelWritten) {

752+

await clearRestartSentinel();

753+

}

748754

const detail = restartMethod.detail ? ` Details: ${restartMethod.detail}` : "";

749755

return {

750756

shouldContinue: false,

Original file line numberDiff line numberDiff line change

@@ -366,6 +366,7 @@ const { updateCommand, updateFinalizeCommand, updateStatusCommand, updateWizardC

366366

const updateCliShared = await import("./update-cli/shared.js");

367367

const { ensureGitCheckout, resolveGitInstallDir } = updateCliShared;

368368

const { spawnSync } = await import("node:child_process");

369+

const { readRestartSentinel } = await import("../infra/restart-sentinel.js");

369370
370371

function requireValue<T>(value: T | undefined, label: string): T {

371372

if (value === undefined) {

@@ -5891,23 +5892,17 @@ describe("update-cli", () => {

58915892

},

58925893

);

58935894
5894-

const raw = await fs.readFile(path.join(stateDir, "restart-sentinel.json"), "utf-8");

5895-

const sentinel = JSON.parse(raw) as {

5896-

payload?: {

5897-

status?: string;

5898-

message?: string | null;

5899-

continuation?: { kind?: string; message?: string };

5900-

stats?: { mode?: string; after?: { version?: string | null } };

5901-

};

5902-

};

5903-

expect(sentinel.payload?.status).toBe("ok");

5904-

expect(sentinel.payload?.message).toBe("Update requested from the agent.");

5905-

expect(sentinel.payload?.continuation).toEqual({

5895+

const sentinel = await readRestartSentinel({

5896+

OPENCLAW_STATE_DIR: stateDir,

5897+

} as NodeJS.ProcessEnv);

5898+

expect(sentinel?.payload.status).toBe("ok");

5899+

expect(sentinel?.payload.message).toBe("Update requested from the agent.");

5900+

expect(sentinel?.payload.continuation).toEqual({

59065901

kind: "agentTurn",

59075902

message: "Check the running version and finish the update report.",

59085903

});

5909-

expect(sentinel.payload?.stats?.mode).toBe("npm");

5910-

expect(sentinel.payload?.stats?.after?.version).toBe("2026.4.24");

5904+

expect(sentinel?.payload.stats?.mode).toBe("npm");

5905+

expect(sentinel?.payload.stats?.after?.version).toBe("2026.4.24");

59115906

});

59125907
59135908

it("marks the control-plane update sentinel failed when restart health verification fails", async () => {

@@ -5966,17 +5961,12 @@ describe("update-cli", () => {

59665961

},

59675962

);

59685963
5969-

const raw = await fs.readFile(path.join(stateDir, "restart-sentinel.json"), "utf-8");

5970-

const sentinel = JSON.parse(raw) as {

5971-

payload?: {

5972-

status?: string;

5973-

continuation?: unknown;

5974-

stats?: { reason?: string | null };

5975-

};

5976-

};

5977-

expect(sentinel.payload?.status).toBe("error");

5978-

expect(sentinel.payload?.stats?.reason).toBe("restart-unhealthy");

5979-

expect(sentinel.payload?.continuation).toBeUndefined();

5964+

const sentinel = await readRestartSentinel({

5965+

OPENCLAW_STATE_DIR: stateDir,

5966+

} as NodeJS.ProcessEnv);

5967+

expect(sentinel?.payload.status).toBe("error");

5968+

expect(sentinel?.payload.stats?.reason).toBe("restart-unhealthy");

5969+

expect(sentinel?.payload.continuation).toBeUndefined();

59805970

expect(defaultRuntime.exit).toHaveBeenCalledWith(1);

59815971

});

59825972
Original file line numberDiff line numberDiff line change

@@ -203,13 +203,12 @@ function buildConfigRestartSentinelPayload(params: {

203203

};

204204

}

205205
206-

async function tryWriteRestartSentinelPayload(

207-

payload: RestartSentinelPayload,

208-

): Promise<string | null> {

206+

async function tryWriteRestartSentinelPayload(payload: RestartSentinelPayload): Promise<boolean> {

209207

try {

210-

return await writeRestartSentinel(payload);

208+

await writeRestartSentinel(payload);

209+

return true;

211210

} catch {

212-

return null;

211+

return false;

213212

}

214213

}

215214

@@ -256,7 +255,7 @@ export async function resolveGatewayConfigRestartWriteResult(params: {

256255

context?: GatewayRequestContext;

257256

}): Promise<{

258257

payload: RestartSentinelPayload;

259-

sentinelPath: string | null;

258+

sentinelPersisted: boolean;

260259

restart: ReturnType<typeof scheduleGatewaySigusr1Restart> | undefined;

261260

}> {

262261

const { sessionKey, note, restartDelayMs, deliveryContext, threadId } =

@@ -270,7 +269,7 @@ export async function resolveGatewayConfigRestartWriteResult(params: {

270269

threadId,

271270

note,

272271

});

273-

const sentinelPath = await tryWriteRestartSentinelPayload(payload);

272+

const sentinelPersisted = await tryWriteRestartSentinelPayload(payload);

274273

const restart = shouldScheduleDirectConfigRestart({

275274

changedPaths: params.changedPaths,

276275

nextConfig: params.nextConfig,

@@ -291,5 +290,5 @@ export async function resolveGatewayConfigRestartWriteResult(params: {

291290

`${params.mode} restart coalesced ${formatControlPlaneActor(params.actor)} delayMs=${restart.delayMs}`,

292291

);

293292

}

294-

return { payload, sentinelPath, restart };

293+

return { payload, sentinelPersisted, restart };

295294

}

Original file line numberDiff line numberDiff line change

@@ -21,9 +21,7 @@ const scheduleGatewaySigusr1RestartMock = vi.fn(() => ({

2121

coalesced: false,

2222

}));

2323

const restartSentinelMocks = vi.hoisted(() => ({

24-

writeRestartSentinel: vi.fn(async (_payload: RestartSentinelPayload) => {

25-

return "/tmp/restart-sentinel.json";

26-

}),

24+

writeRestartSentinel: vi.fn(async (_payload: RestartSentinelPayload) => undefined),

2725

}));

2826
2927

vi.mock("../../config/config.js", async () => {

Original file line numberDiff line numberDiff line change

@@ -594,7 +594,7 @@ async function respondWithConfigRestartWrite(params: {

594594

uiHints: ConfigRedactionHints;

595595

}): Promise<void> {

596596

clearConfigSchemaResponseCache();

597-

const { payload, sentinelPath, restart } = await resolveGatewayConfigRestartWriteResult({

597+

const { payload, sentinelPersisted, restart } = await resolveGatewayConfigRestartWriteResult({

598598

requestParams: params.requestParams,

599599

kind: params.kind,

600600

mode: params.mode,

@@ -612,7 +612,7 @@ async function respondWithConfigRestartWrite(params: {

612612

config: redactConfigObject(params.writeResult.config, params.uiHints),

613613

restart,

614614

sentinel: {

615-

path: sentinelPath,

615+

persisted: sentinelPersisted,

616616

payload,

617617

},

618618

},