Skip to content

Commit 3a05c71

Browse files
committed
fix(gateway): avoid sync restart sentinel startup probes
1 parent da0daa2 commit 3a05c71

3 files changed

Lines changed: 50 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Docs: https://docs.openclaw.ai
3535

3636
### Fixes
3737

38+
- Gateway: avoid synchronous restart-sentinel state probes during post-attach startup, preventing slow Windows or redirected state directories from blocking channel turns. Fixes #79264. Thanks @liyi58.
3839
- Agents/image: honor explicit `image` tool model overrides even when `agents.defaults.imageModel` is unset, restoring one-off vision calls for configured multimodal providers. Fixes #79341. Thanks @haumanto.
3940
- Codex app-server: preserve prompt-local current-turn context through context-engine prompt projection, so replied-to Telegram messages stay visible to the Codex model input.
4041
- Telegram: pass agent-scoped media roots through gateway message actions so workspace-local media from the active agent is not rejected as cross-agent access. Thanks @frankekn.

src/gateway/server-startup-post-attach.test.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ describe("startGatewayPostAttachRuntime", () => {
308308
fs.rmSync(stateDir, { recursive: true, force: true });
309309
});
310310

311-
it("expands tilde-based restart sentinel state paths", () => {
311+
it("expands tilde-based restart sentinel state paths", async () => {
312312
const osHome = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-home-"));
313313
try {
314314
const openclawHome = path.join(osHome, "openclaw-home");
@@ -317,7 +317,7 @@ describe("startGatewayPostAttachRuntime", () => {
317317
fs.writeFileSync(path.join(stateDirFromHome, "restart-sentinel.json"), "{}\n");
318318

319319
expect(
320-
__testing.hasRestartSentinelFileFast({
320+
await __testing.hasRestartSentinelFileFast({
321321
HOME: osHome,
322322
OPENCLAW_HOME: "~/openclaw-home",
323323
} as NodeJS.ProcessEnv),
@@ -328,7 +328,7 @@ describe("startGatewayPostAttachRuntime", () => {
328328
fs.writeFileSync(path.join(backslashStateDir, "restart-sentinel.json"), "{}\n");
329329

330330
expect(
331-
__testing.hasRestartSentinelFileFast({
331+
await __testing.hasRestartSentinelFileFast({
332332
HOME: osHome,
333333
OPENCLAW_STATE_DIR: "~\\openclaw-state",
334334
} as NodeJS.ProcessEnv),
@@ -338,6 +338,34 @@ describe("startGatewayPostAttachRuntime", () => {
338338
}
339339
});
340340

341+
it("avoids sync filesystem probes while checking restart sentinel presence", async () => {
342+
const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-async-sentinel-"));
343+
try {
344+
fs.writeFileSync(path.join(stateDir, "restart-sentinel.json"), "{}\n");
345+
const actualExistsSync = fs.existsSync;
346+
const existsSync = vi.spyOn(fs, "existsSync").mockImplementation((candidate) => {
347+
if (String(candidate).startsWith(stateDir)) {
348+
throw new Error("sync restart sentinel probe");
349+
}
350+
return actualExistsSync(candidate);
351+
});
352+
try {
353+
await expect(
354+
__testing.hasRestartSentinelFileFast({
355+
OPENCLAW_STATE_DIR: stateDir,
356+
} as NodeJS.ProcessEnv),
357+
).resolves.toBe(true);
358+
expect(
359+
existsSync.mock.calls.filter((call) => String(call[0]).startsWith(stateDir)),
360+
).toHaveLength(0);
361+
} finally {
362+
existsSync.mockRestore();
363+
}
364+
} finally {
365+
fs.rmSync(stateDir, { recursive: true, force: true });
366+
}
367+
});
368+
341369
it("loads deferred startup plugins before channel sidecars", async () => {
342370
const events: string[] = [];
343371
const loadedPluginRegistry = {

src/gateway/server-startup-post-attach.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,18 @@ function schedulePostReadySidecarTask(params: {
144144
handle.unref?.();
145145
}
146146

147-
function resolveRestartSentinelPathFast(env: NodeJS.ProcessEnv = process.env): string {
147+
async function pathExists(filePath: string): Promise<boolean> {
148+
try {
149+
await fs.promises.access(filePath);
150+
return true;
151+
} catch {
152+
return false;
153+
}
154+
}
155+
156+
async function resolveRestartSentinelPathFast(
157+
env: NodeJS.ProcessEnv = process.env,
158+
): Promise<string> {
148159
const normalizePathEnv = (value: string | undefined) => {
149160
const trimmed = value?.trim();
150161
return trimmed && trimmed !== "undefined" && trimmed !== "null" ? trimmed : undefined;
@@ -172,19 +183,19 @@ function resolveRestartSentinelPathFast(env: NodeJS.ProcessEnv = process.env): s
172183
}
173184
const home = resolveHome();
174185
const newStateDir = path.join(home, ".openclaw");
175-
if (env.OPENCLAW_TEST_FAST === "1" || fs.existsSync(newStateDir)) {
186+
if (env.OPENCLAW_TEST_FAST === "1" || (await pathExists(newStateDir))) {
176187
return path.join(newStateDir, RESTART_SENTINEL_FILENAME);
177188
}
178189
const legacyStateDir = path.join(home, ".clawdbot");
179-
if (fs.existsSync(legacyStateDir)) {
190+
if (await pathExists(legacyStateDir)) {
180191
return path.join(legacyStateDir, RESTART_SENTINEL_FILENAME);
181192
}
182193
return path.join(newStateDir, RESTART_SENTINEL_FILENAME);
183194
}
184195

185-
function hasRestartSentinelFileFast(env: NodeJS.ProcessEnv = process.env): boolean {
196+
async function hasRestartSentinelFileFast(env: NodeJS.ProcessEnv = process.env): Promise<boolean> {
186197
try {
187-
return fs.existsSync(resolveRestartSentinelPathFast(env));
198+
return await pathExists(await resolveRestartSentinelPathFast(env));
188199
} catch {
189200
return false;
190201
}
@@ -193,7 +204,7 @@ function hasRestartSentinelFileFast(env: NodeJS.ProcessEnv = process.env): boole
193204
async function refreshLatestUpdateRestartSentinelIfPresent(): Promise<Awaited<
194205
ReturnType<typeof refreshLatestUpdateRestartSentinel>
195206
> | null> {
196-
if (!hasRestartSentinelFileFast()) {
207+
if (!(await hasRestartSentinelFileFast())) {
197208
return null;
198209
}
199210
return await (await import("./server-restart-sentinel.js")).refreshLatestUpdateRestartSentinel();
@@ -589,7 +600,7 @@ export async function startGatewaySidecars(params: {
589600
if (!shouldCheckRestartSentinel()) {
590601
return;
591602
}
592-
if (!hasRestartSentinelFileFast()) {
603+
if (!(await hasRestartSentinelFileFast())) {
593604
return;
594605
}
595606
setTimeout(() => {

0 commit comments

Comments
 (0)