diff --git a/CHANGELOG.md b/CHANGELOG.md index 85ca536816e..824e78c9ced 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -113,7 +113,7 @@ Docs: https://docs.openclaw.ai - Codex harness: forward OpenClaw workspace bootstrap files such as `SOUL.md` through native Codex config instructions while leaving `AGENTS.md` to Codex project-doc discovery. Fixes #76273. Thanks @zknicker. - Parallels/Windows update smoke: escape the stale post-swap import regex in the generated PowerShell script so expected `ERR_MODULE_NOT_FOUND` update handoffs continue to post-update health checks. (#75315) - Slack: allow draft preview streaming in top-level DMs when `replyToMode` is `off` while keeping Slack native streaming and assistant thread status gated on reply threads. Fixes #56480. (#56544) Thanks @HangGlidersRule. -- Control UI/chat: remove the delete-confirm popover outside-click listener on every dismiss path, so Cancel, Delete, outside clicks, and same-button toggles no longer leave stale document listeners behind. Refs #75590 and #69982. Thanks @Ricardo-M-L. +- Memory-core: treat exhausted file watcher limits as non-fatal for builtin memory auto-sync while preserving fatal handling for unrelated disk-full errors. (#73357) Thanks @solodmd. ## 2026.5.2 diff --git a/src/infra/unhandled-rejections.test.ts b/src/infra/unhandled-rejections.test.ts index fbe5bcc02db..b852fa1cb8a 100644 --- a/src/infra/unhandled-rejections.test.ts +++ b/src/infra/unhandled-rejections.test.ts @@ -289,6 +289,13 @@ describe("isTransientFileWatchError", () => { expect(isTransientFileWatchError(error)).toBe(false); }); + it("returns false for message-only disk full without watch indicator", () => { + expect(isTransientFileWatchError(new Error("write failed: no space left on device"))).toBe( + false, + ); + expect(isTransientFileWatchError(new Error("ENOSPC: no space left on device"))).toBe(false); + }); + it("returns true for 'no space left on device' message with watcher context", () => { const error = new Error("file watcher: no space left on device"); expect(isTransientFileWatchError(error)).toBe(true); @@ -386,4 +393,13 @@ describe("isTransientUnhandledRejectionError", () => { }); expect(isTransientUnhandledRejectionError(error)).toBe(false); }); + + it("returns false for code-less disk full messages without watch indicator", () => { + expect( + isTransientUnhandledRejectionError(new Error("write failed: no space left on device")), + ).toBe(false); + expect(isTransientUnhandledRejectionError(new Error("ENOSPC: no space left on device"))).toBe( + false, + ); + }); }); diff --git a/src/infra/unhandled-rejections.ts b/src/infra/unhandled-rejections.ts index ec1e0800bc5..9dbf431a7e6 100644 --- a/src/infra/unhandled-rejections.ts +++ b/src/infra/unhandled-rejections.ts @@ -365,6 +365,13 @@ export function isTransientFileWatchError(err: unknown): boolean { return false; } + const hasFileWatchSignal = (message: string) => + message.includes("inotify") || + message.includes("watcher") || + message.includes("file watcher") || + message.includes("watch limit") || + message.includes("max watches"); + for (const candidate of collectNestedUnhandledErrorCandidates(err)) { // Skip non-object candidates early if (!candidate || typeof candidate !== "object") { @@ -379,13 +386,7 @@ export function isTransientFileWatchError(err: unknown): boolean { // ENOSPC requires both the code AND a watch/inotify message indicator // to avoid misclassifying general disk-full errors as transient watcher errors. if (code === "ENOSPC") { - if ( - message.includes("inotify") || - message.includes("watcher") || - message.includes("file watcher") || - message.includes("watch limit") || - message.includes("max watches") - ) { + if (hasFileWatchSignal(message)) { return true; } // ENOSPC without watch indicator is not classified here @@ -397,8 +398,8 @@ export function isTransientFileWatchError(err: unknown): boolean { continue; } if ( - message.includes("no space left on device") || - message.includes("enosp") || + ((message.includes("no space left on device") || message.includes("enosp")) && + hasFileWatchSignal(message)) || message.includes("inotify watches") || message.includes("file watcher") || message.includes("watcher error")