fix: retry config snapshot after rejection (#83944)

Summary:
- This PR clears the cached CLI config snapshot promise when a read rejects, adds a reject-retry-cache regression test, and adds an Unreleased changelog entry.
- Reproducibility: yes. Current main clearly caches the first snapshot-read promise, and the source PR supplied a focused reject, recover, cached-success probe; I did not rerun it in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix: retry config snapshot after rejection

Validation:
- ClawSweeper review passed for head a46b5ec5c7.
- Required merge gates passed before the squash merge.

Prepared head SHA: a46b5ec5c7
Review: https://github.com/openclaw/openclaw/pull/83944#issuecomment-4484051060

Co-authored-by: honor2030 <19909783+honor2030@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
This commit is contained in:
clawsweeper[bot]
2026-05-19 03:28:26 +00:00
committed by GitHub
parent 1f794d2816
commit cbaf858227
3 changed files with 35 additions and 1 deletions

View File

@@ -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"]);

View File

@@ -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;
}