diff --git a/CHANGELOG.md b/CHANGELOG.md index ac9c988cfc8..b06b2d87d38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Agents/subagents: skip stale embedded-run wake probes for dormant completion requesters, so late subagent completions go straight to requester-agent/direct handoff instead of producing `reason=no_active_run` queue noise. (#82964) Thanks @galiniliev. +- CLI: retry config snapshot reads after a transient failure so one rejected read no longer poisons later commands in the same process. (#83931) Thanks @honor2030. - WhatsApp: drain pending outbound deliveries on a 30s periodic timer in addition to the reconnect handler, so messages enqueued while the provider is already connected no longer wait for the next reconnect to send. (#79083) Thanks @Oviemudiaga. ## 2026.5.19 diff --git a/src/cli/program/config-guard.test.ts b/src/cli/program/config-guard.test.ts index 2f033a25ed3..3462b39f82c 100644 --- a/src/cli/program/config-guard.test.ts +++ b/src/cli/program/config-guard.test.ts @@ -130,6 +130,31 @@ describe("ensureConfigReady", () => { ); }); + it("retries the cached config snapshot after a read rejection", async () => { + const originalVitest = process.env.VITEST; + process.env.VITEST = "false"; + const transientError = new Error("temporary config read failure"); + const recoveredSnapshot = makeSnapshot(); + readConfigFileSnapshotMock + .mockRejectedValueOnce(transientError) + .mockResolvedValueOnce(recoveredSnapshot); + + try { + await expect(runEnsureConfigReady(["status"])).rejects.toThrow(transientError); + await expect(runEnsureConfigReady(["status"])).resolves.toBeDefined(); + await expect(runEnsureConfigReady(["status"])).resolves.toBeDefined(); + } finally { + if (originalVitest === undefined) { + delete process.env.VITEST; + } else { + process.env.VITEST = originalVitest; + } + } + + expect(readConfigFileSnapshotMock).toHaveBeenCalledTimes(2); + expect(setRuntimeConfigSnapshotMock).toHaveBeenCalledWith(undefined, undefined); + }); + it("exits for invalid config on non-allowlisted commands", async () => { setInvalidSnapshot(); const runtime = await runEnsureConfigReady(["message"]); diff --git a/src/cli/program/config-guard.ts b/src/cli/program/config-guard.ts index 8b099fab732..770059f0b1a 100644 --- a/src/cli/program/config-guard.ts +++ b/src/cli/program/config-guard.ts @@ -30,7 +30,15 @@ async function getConfigSnapshot() { if (process.env.VITEST === "true") { return readConfigFileSnapshot(); } - configSnapshotPromise ??= readConfigFileSnapshot(); + if (!configSnapshotPromise) { + const pendingSnapshot = readConfigFileSnapshot(); + configSnapshotPromise = pendingSnapshot; + pendingSnapshot.catch(() => { + if (configSnapshotPromise === pendingSnapshot) { + configSnapshotPromise = null; + } + }); + } return configSnapshotPromise; }