Skip to content

fix(memory-core): fix macOS chokidar glob issue by watching memory dir directly#64711

Merged
vincentkoc merged 4 commits into
openclaw:mainfrom
jasonxargs-boop:fix/macos-chokidar-glob
Apr 12, 2026
Merged

fix(memory-core): fix macOS chokidar glob issue by watching memory dir directly#64711
vincentkoc merged 4 commits into
openclaw:mainfrom
jasonxargs-boop:fix/macos-chokidar-glob

Conversation

@jasonxargs-boop

Copy link
Copy Markdown
Contributor

On some macOS + Node 25 environments, chokidar fails to watch files when using recursive glob patterns like 'memory/**/*.md'. Changing to watch the folder directly resolves the issue while keeping same recursive watch behavior.

@greptile-apps

greptile-apps Bot commented Apr 11, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This one-line fix replaces the recursive glob memory/**/*.md with a bare directory path memory to work around a reported chokidar issue on macOS + Node 25. The behavioral intent is correct — chokidar watches directories recursively by default — but two side-effects are worth noting: the ignored callback (shouldIgnoreMemoryWatchPath) only filters by directory name and has no extension check, so non-.md files in the memory folder will now trigger spurious markDirty() syncs; and the parallel additionalPaths code path still constructs glob strings (line 358), so the underlying issue would still surface there.

Confidence Score: 5/5

Safe to merge; only P2 style/quality findings remain.

Both findings are P2 — spurious syncs on non-.md file changes and inconsistent treatment of additionalPaths. Neither causes data loss or incorrect sync behavior; they are performance/cleanup concerns.

extensions/memory-core/src/memory/manager-sync-ops.ts — review the ignored callback and additionalPaths glob handling.

Comments Outside Diff (1)

  1. extensions/memory-core/src/memory/manager-sync-ops.ts, line 357-358 (link)

    P2 additionalPaths directories still use glob patterns

    The fix only changes the hardcoded memory directory path; additionalPaths directories are still added as path.join(entry, "**", "*.md") globs on line 358. If the chokidar + Node 25 bug genuinely affects recursive glob patterns, this code path has the same problem and would need the same treatment.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: extensions/memory-core/src/memory/manager-sync-ops.ts
    Line: 357-358
    
    Comment:
    **`additionalPaths` directories still use glob patterns**
    
    The fix only changes the hardcoded `memory` directory path; `additionalPaths` directories are still added as `path.join(entry, "**", "*.md")` globs on line 358. If the chokidar + Node 25 bug genuinely affects recursive glob patterns, this code path has the same problem and would need the same treatment.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/memory-core/src/memory/manager-sync-ops.ts
Line: 348

Comment:
**Watching all file types, not just `.md`**

The old glob `memory/**/*.md` meant chokidar only emitted events for Markdown files. The new path `memory` is a bare directory, and `shouldIgnoreMemoryWatchPath` only filters on directory-name segments (`.git`, `node_modules`, etc.) — it has no extension check. Any file change inside `memory` (e.g. `.tmp`, `.json`, editor swap files) will now call `markDirty()` and schedule an unnecessary sync cycle. Consider adding an extension check in the `ignored` callback, or passing a custom filter, to preserve the `.md`-only intent.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: extensions/memory-core/src/memory/manager-sync-ops.ts
Line: 357-358

Comment:
**`additionalPaths` directories still use glob patterns**

The fix only changes the hardcoded `memory` directory path; `additionalPaths` directories are still added as `path.join(entry, "**", "*.md")` globs on line 358. If the chokidar + Node 25 bug genuinely affects recursive glob patterns, this code path has the same problem and would need the same treatment.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(memory-core): fix macOS chokidar glo..." | Re-trigger Greptile

Comment thread extensions/memory-core/src/memory/manager-sync-ops.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8ee2e95b18

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread extensions/memory-core/src/memory/manager-sync-ops.ts
@vincentkoc
vincentkoc force-pushed the fix/macos-chokidar-glob branch from 8ee2e95 to 8bca71a Compare April 12, 2026 17:16
@vincentkoc vincentkoc self-assigned this Apr 12, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8bca71aeb4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread extensions/memory-core/src/memory/manager-sync-ops.ts Outdated
@vincentkoc
vincentkoc merged commit 2204753 into openclaw:main Apr 12, 2026
8 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 564f89daae

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +105 to +109
if (stats?.isDirectory?.()) {
return false;
}
const extension = normalizeLowercaseStringOrEmpty(path.extname(normalized));
if (extension.length === 0 || extension === ".md") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not drop directory traversal for dotted folder names

Chokidar v5 calls the ignored predicate before stat information is available for newly discovered entries, so directory paths can arrive without stats. In that case, a folder like memory/topic.v1 falls through to extension filtering and is treated as a non-markdown file, causing it to be ignored and preventing traversal into topic.v1; markdown files created under that folder never emit watch events, so watch-driven memory sync can miss real content updates.

Useful? React with 👍 / 👎.

Comment on lines +109 to +110
if (extension.length === 0 || extension === ".md") {
return false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude extensionless files from watch-triggered syncs

The new filter allows any path with path.extname(...) === "", which includes common non-indexable files such as .DS_Store, .env, and README. Those files now still fire add/change events under the watched memory directory and trigger watch syncs even though they can never be indexed, so this leaves a churn path that can cause unnecessary repeated sync work on macOS/editor-heavy workspaces.

Useful? React with 👍 / 👎.

jasonxargs-boop added a commit to jasonxargs-boop/openclaw that referenced this pull request Apr 16, 2026
…macOS/Node 25

When watching the memory directory directly (introduced in openclaw#64711 to fix
recursive glob issues on macOS + Node 25), chokidar fires events for all
file types within the directory tree — including .json state files written
by the dreaming subsystem under memory/.dreams/.

Previously, markDirty had no extension check, so every .json write from
dreaming (phase-signals.json, short-term-recall.json, etc.) would set
dirty=true and trigger a needless sync. More critically, under high I/O
load (e.g. dreaming writing many state files rapidly), the debounce timer
kept being reset, which could delay or suppress the sync for .md files.

Fix: add an explicit extension check in markDirty so only .md files (and
configured multimodal extensions) trigger the dirty flag and schedule a
sync. Non-markdown events are silently ignored.

This completes the fix started in openclaw#64711 and addresses the reported
behaviour where newly created .md files were not being indexed until a
manual openclaw memory index or memory_search was performed.
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
…r directly (openclaw#64711)

* fix(memory-core): fix macOS chokidar glob issue by watching memory dir directly

* fix(memory-core): ignore non-markdown memory watch churn

* fix(memory-core): allow multimodal watch events

* test(memory-core): type watcher ignore callback

---------

Co-authored-by: Vincent Koc <[email protected]>
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…r directly (openclaw#64711)

* fix(memory-core): fix macOS chokidar glob issue by watching memory dir directly

* fix(memory-core): ignore non-markdown memory watch churn

* fix(memory-core): allow multimodal watch events

* test(memory-core): type watcher ignore callback

---------

Co-authored-by: Vincent Koc <[email protected]>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…r directly (openclaw#64711)

* fix(memory-core): fix macOS chokidar glob issue by watching memory dir directly

* fix(memory-core): ignore non-markdown memory watch churn

* fix(memory-core): allow multimodal watch events

* test(memory-core): type watcher ignore callback

---------

Co-authored-by: Vincent Koc <[email protected]>
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
…r directly (openclaw#64711)

* fix(memory-core): fix macOS chokidar glob issue by watching memory dir directly

* fix(memory-core): ignore non-markdown memory watch churn

* fix(memory-core): allow multimodal watch events

* test(memory-core): type watcher ignore callback

---------

Co-authored-by: Vincent Koc <[email protected]>
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
…r directly (openclaw#64711)

* fix(memory-core): fix macOS chokidar glob issue by watching memory dir directly

* fix(memory-core): ignore non-markdown memory watch churn

* fix(memory-core): allow multimodal watch events

* test(memory-core): type watcher ignore callback

---------

Co-authored-by: Vincent Koc <[email protected]>
osolmaz pushed a commit to lukeboyett/openclaw that referenced this pull request May 26, 2026
…ories

Closes openclaw#86613.

A single authorized POST /tools/invoke memory_search call against a
workspace with multi-thousand .md files under <workspace>/memory/ opens
one read-only FD per file (~12,400 in our captures), never released
until process exit. Root cause: chokidar v5 calls fs.watch(path) per
filesystem node (handler.js:126), no recursive option. On macOS each
per-file fs.watch opens a kqueue VNODE FD that lsof reports as a
regular-file REG FD. So one watcher per .md file = one FD per file =
12k+ FDs.

Switch directory watch paths to Node >= 22 native recursive
fs.watch(dir, { recursive: true }) — one watcher per directory via
FSEvents on macOS / inotify recursive on Linux / ReadDirectoryChangesW
on Windows. Individual file paths (MEMORY.md, file-typed extraPaths)
continue through chokidar. Same dirty-event semantics, different
backend, small constant FD profile regardless of tree size.

Regression introduced by openclaw#64711 (jasonxargs-boop, 2026-04-13) which
changed the chokidar watch target from glob memory/**/*.md to bare
directory memory/, making the recursive walk implicit. PR openclaw#81802
(frankekn, 2026-05-15) removed awaitWriteFinish and added watch-settle
on the same path; that reduced write-polling pressure but did not
address the per-node fs.watch allocation (verification used 1,000 files,
well under the storm-producing scale).

Lab verification on clean upstream-main + synthetic 12,391-file
workspace: 0 → 1 REG FD (MEMORY.md only; 12,391 file events covered by
one recursive kqueue/FSEvents watcher), flat plateau, second
memory_search returns 200 OK with zero additional FDs.

Complementary to openclaw#86345 which attacks the same failure class by
bounding INDEX_CACHE lifetime; this PR is non-overlapping in
manager-sync-ops.ts and touches a disjoint region of manager.ts.

Changes:
- manager-sync-ops.ts: split watchPaths by dir/file; native recursive
  fs.watch for dirs with shouldIgnoreMemoryWatchPath filter and
  per-event lstat; chokidar retained for files. New
  TEST_MEMORY_NATIVE_WATCH_FACTORY_KEY for test injection. Native
  creation failure falls back into chokidar set. Null filename
  (Node platform caveat) triggers broad markDirty. Runtime error
  closes + removes + marks dirty. Idempotence guard expanded.
  Symlink policy preserved (extraPaths-only skip).
- manager.ts: close() tears down nativeMemoryWatchers in addition to
  chokidar watcher.
- manager.watcher-config.test.ts: nativeWatchMock paired with new
  factory key; assertions split between chokidar (files) and native
  (dirs); new cases for rename/change dispatch, null filename, native
  creation failure fallback, runtime error close, ensureWatcher
  re-entrancy guard.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
osolmaz pushed a commit to lukeboyett/openclaw that referenced this pull request May 26, 2026
…ories

Closes openclaw#86613.

A single authorized POST /tools/invoke memory_search call against a
workspace with multi-thousand .md files under <workspace>/memory/ opens
one read-only FD per file (~12,400 in our captures), never released
until process exit. Root cause: chokidar v5 calls fs.watch(path) per
filesystem node (handler.js:126), no recursive option. On macOS each
per-file fs.watch opens a kqueue VNODE FD that lsof reports as a
regular-file REG FD. So one watcher per .md file = one FD per file =
12k+ FDs.

Switch directory watch paths to Node >= 22 native recursive
fs.watch(dir, { recursive: true }) — one watcher per directory via
FSEvents on macOS / inotify recursive on Linux / ReadDirectoryChangesW
on Windows. Individual file paths (MEMORY.md, file-typed extraPaths)
continue through chokidar. Same dirty-event semantics, different
backend, small constant FD profile regardless of tree size.

Regression introduced by openclaw#64711 (jasonxargs-boop, 2026-04-13) which
changed the chokidar watch target from glob memory/**/*.md to bare
directory memory/, making the recursive walk implicit. PR openclaw#81802
(frankekn, 2026-05-15) removed awaitWriteFinish and added watch-settle
on the same path; that reduced write-polling pressure but did not
address the per-node fs.watch allocation (verification used 1,000 files,
well under the storm-producing scale).

Lab verification on clean upstream-main + synthetic 12,391-file
workspace: 0 → 1 REG FD (MEMORY.md only; 12,391 file events covered by
one recursive kqueue/FSEvents watcher), flat plateau, second
memory_search returns 200 OK with zero additional FDs.

Complementary to openclaw#86345 which attacks the same failure class by
bounding INDEX_CACHE lifetime; this PR is non-overlapping in
manager-sync-ops.ts and touches a disjoint region of manager.ts.

Changes:
- manager-sync-ops.ts: split watchPaths by dir/file; native recursive
  fs.watch for dirs with shouldIgnoreMemoryWatchPath filter and
  per-event lstat; chokidar retained for files. New
  TEST_MEMORY_NATIVE_WATCH_FACTORY_KEY for test injection. Native
  creation failure falls back into chokidar set. Null filename
  (Node platform caveat) triggers broad markDirty. Runtime error
  closes + removes + marks dirty. Idempotence guard expanded.
  Symlink policy preserved (extraPaths-only skip).
- manager.ts: close() tears down nativeMemoryWatchers in addition to
  chokidar watcher.
- manager.watcher-config.test.ts: nativeWatchMock paired with new
  factory key; assertions split between chokidar (files) and native
  (dirs); new cases for rename/change dispatch, null filename, native
  creation failure fallback, runtime error close, ensureWatcher
  re-entrancy guard.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
osolmaz pushed a commit to lukeboyett/openclaw that referenced this pull request May 26, 2026
…ories

Closes openclaw#86613.

A single authorized POST /tools/invoke memory_search call against a
workspace with multi-thousand .md files under <workspace>/memory/ opens
one read-only FD per file (~12,400 in our captures), never released
until process exit. Root cause: chokidar v5 calls fs.watch(path) per
filesystem node (handler.js:126), no recursive option. On macOS each
per-file fs.watch opens a kqueue VNODE FD that lsof reports as a
regular-file REG FD. So one watcher per .md file = one FD per file =
12k+ FDs.

Switch directory watch paths to Node >= 22 native recursive
fs.watch(dir, { recursive: true }) — one watcher per directory via
FSEvents on macOS / inotify recursive on Linux / ReadDirectoryChangesW
on Windows. Individual file paths (MEMORY.md, file-typed extraPaths)
continue through chokidar. Same dirty-event semantics, different
backend, small constant FD profile regardless of tree size.

Regression introduced by openclaw#64711 (jasonxargs-boop, 2026-04-13) which
changed the chokidar watch target from glob memory/**/*.md to bare
directory memory/, making the recursive walk implicit. PR openclaw#81802
(frankekn, 2026-05-15) removed awaitWriteFinish and added watch-settle
on the same path; that reduced write-polling pressure but did not
address the per-node fs.watch allocation (verification used 1,000 files,
well under the storm-producing scale).

Lab verification on clean upstream-main + synthetic 12,391-file
workspace: 0 → 1 REG FD (MEMORY.md only; 12,391 file events covered by
one recursive kqueue/FSEvents watcher), flat plateau, second
memory_search returns 200 OK with zero additional FDs.

Complementary to openclaw#86345 which attacks the same failure class by
bounding INDEX_CACHE lifetime; this PR is non-overlapping in
manager-sync-ops.ts and touches a disjoint region of manager.ts.

Changes:
- manager-sync-ops.ts: split watchPaths by dir/file; native recursive
  fs.watch for dirs with shouldIgnoreMemoryWatchPath filter and
  per-event lstat; chokidar retained for files. New
  TEST_MEMORY_NATIVE_WATCH_FACTORY_KEY for test injection. Native
  creation failure falls back into chokidar set. Null filename
  (Node platform caveat) triggers broad markDirty. Runtime error
  closes + removes + marks dirty. Idempotence guard expanded.
  Symlink policy preserved (extraPaths-only skip).
- manager.ts: close() tears down nativeMemoryWatchers in addition to
  chokidar watcher.
- manager.watcher-config.test.ts: nativeWatchMock paired with new
  factory key; assertions split between chokidar (files) and native
  (dirs); new cases for rename/change dispatch, null filename, native
  creation failure fallback, runtime error close, ensureWatcher
  re-entrancy guard.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
…r directly (openclaw#64711)

* fix(memory-core): fix macOS chokidar glob issue by watching memory dir directly

* fix(memory-core): ignore non-markdown memory watch churn

* fix(memory-core): allow multimodal watch events

* test(memory-core): type watcher ignore callback

---------

Co-authored-by: Vincent Koc <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants