mirror of https://github.com/openclaw/openclaw.git
fix(agents): prevent unhandled rejection when compaction retry times out [AI] (#57451)
* fix(agents): prevent unhandled rejection when compaction retry times out * fix(agents): preserve compaction retry wait errors * chore(changelog): add compaction retry timeout entry --------- Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
parent
aee61dcee0
commit
6b255b4dec
|
|
@ -91,6 +91,7 @@ Docs: https://docs.openclaw.ai
|
|||
- Status: fix cache hit rate exceeding 100% by deriving denominator from prompt-side token fields instead of potentially undersized totalTokens. Fixes #26643.
|
||||
- Config/update: stop `openclaw doctor` write-backs from persisting plugin-injected channel defaults, so `openclaw update` no longer seeds config keys that later break service refresh validation. (#56834) Thanks @openperf.
|
||||
- Agents/Anthropic failover: treat Anthropic `api_error` payloads with `An unexpected error occurred while processing the response` as transient so retry/fallback can engage instead of surfacing a terminal failure. (#57441) Thanks @zijiess and @vincentkoc.
|
||||
- Agents/compaction: keep late compaction-retry rejections handled after the aggregate timeout path wins without swallowing real pre-timeout wait failures, so timed-out retries no longer surface an unhandled rejection on later unsubscribe. (#57451) Thanks @mpz4life and @vincentkoc.
|
||||
|
||||
## 2026.3.28
|
||||
|
||||
|
|
|
|||
|
|
@ -116,6 +116,46 @@ describe("waitForCompactionRetryWithAggregateTimeout", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("propagates immediate waitForCompactionRetry failures", async () => {
|
||||
await withFakeTimers(async () => {
|
||||
const waitError = new Error("compaction wait failed");
|
||||
const waitForCompactionRetry = vi.fn(async () => {
|
||||
throw waitError;
|
||||
});
|
||||
const params = buildAggregateTimeoutParams({ waitForCompactionRetry });
|
||||
|
||||
await expect(waitForCompactionRetryWithAggregateTimeout(params)).rejects.toThrow(
|
||||
"compaction wait failed",
|
||||
);
|
||||
|
||||
expectClearedTimeoutState(params.onTimeout, false);
|
||||
});
|
||||
});
|
||||
|
||||
it("handles waitForCompactionRetry rejection after timeout wins", async () => {
|
||||
await withFakeTimers(async () => {
|
||||
let rejectWait: ((error: Error) => void) | undefined;
|
||||
const waitForCompactionRetry = vi.fn(
|
||||
async () =>
|
||||
await new Promise<void>((_resolve, reject) => {
|
||||
rejectWait = reject;
|
||||
}),
|
||||
);
|
||||
const params = buildAggregateTimeoutParams({ waitForCompactionRetry });
|
||||
|
||||
const resultPromise = waitForCompactionRetryWithAggregateTimeout(params);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(60_000);
|
||||
const result = await resultPromise;
|
||||
|
||||
rejectWait?.(new Error("cancelled after timeout"));
|
||||
await Promise.resolve();
|
||||
|
||||
expect(result.timedOut).toBe(true);
|
||||
expectClearedTimeoutState(params.onTimeout, true);
|
||||
});
|
||||
});
|
||||
|
||||
it("propagates abort errors from abortable and clears timer", async () => {
|
||||
await withFakeTimers(async () => {
|
||||
const abortError = new Error("aborted");
|
||||
|
|
|
|||
|
|
@ -13,7 +13,12 @@ export async function waitForCompactionRetryWithAggregateTimeout(params: {
|
|||
const timeoutMs = Number.isFinite(timeoutMsRaw) ? Math.max(1, Math.floor(timeoutMsRaw)) : 1;
|
||||
|
||||
let timedOut = false;
|
||||
const waitPromise = params.waitForCompactionRetry().then(() => "done" as const);
|
||||
// Reflect the retry promise so late rejections after a timeout stay handled
|
||||
// without masking failures that settle before the timeout path wins.
|
||||
const waitPromise = params.waitForCompactionRetry().then(
|
||||
() => ({ kind: "done" as const }),
|
||||
(error: unknown) => ({ kind: "rejected" as const, error }),
|
||||
);
|
||||
|
||||
while (true) {
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
|
|
@ -27,8 +32,11 @@ export async function waitForCompactionRetryWithAggregateTimeout(params: {
|
|||
]),
|
||||
);
|
||||
|
||||
if (result === "done") {
|
||||
break;
|
||||
if (result !== "timeout") {
|
||||
if (result.kind === "done") {
|
||||
break;
|
||||
}
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
// Keep extending the timeout window while compaction is actively running.
|
||||
|
|
|
|||
Loading…
Reference in New Issue