Skip to content

fix(watchers): revive dead skill and memory file watchers#113

Merged
rich7420 merged 2 commits into
mainfrom
fix/dead-file-watchers
Jul 9, 2026
Merged

fix(watchers): revive dead skill and memory file watchers#113
rich7420 merged 2 commits into
mainfrom
fix/dead-file-watchers

Conversation

@rich7420

@rich7420 rich7420 commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

chokidar v4+ dropped glob support, but two watchers still handed it literal glob
paths (skills/watcher.ts, memory/index.ts). In v5 a path containing * is
treated as a literal filename that matches nothing, so both watchers registered
successfully — returned a live FSWatcher, attached their .on("all") handler —
yet never fired. The failure was invisible: no log, no error.

This restores them by watching the containing directories (non-glob) and
filtering events in the handler, and hardens the ignore matching so legitimately
named skill/memory files are no longer pruned.

Before

  • skills/watcher.ts watched ~/.hawky/skills/*/SKILL.md and
    <ws>/skills/*/SKILL.md. Both matched nothing → skill hot-reload was fully
    dead
    : editing/adding/removing a skill on disk had no effect until a full
    process restart.
  • memory/index.ts watched <ws>/*.md (dead glob) plus <ws>/memory and the
    sessions dir (real dirs, worked). So editing a top-level .md such as
    HAWKY.md never triggered a memory reindex — the index silently drifted
    from the files it claims to mirror.
  • The ignore list used unanchored substring regexes (/build/, /dist/,
    /node_modules/, …). Once a watcher is live, these prune any path containing
    the substring — e.g. a skill named prompt-builder (contains "build") would be
    silently skipped.

After

  • Skills watcher watches the skill root directories and reacts only to files
    whose basename is SKILL.md, so edits, adds and removes propagate within one
    debounce window.
  • Memory watcher watches the workspace root + sessions dir and filters changed
    paths to exactly what the indexer consumes (top-level .md, the memory/
    subtree, session .jsonl), so top-level .md edits now reindex.
  • Ignore matching is now by exact path segment scoped below the watch roots,
    so prompt-builder (or even a skill named build) and a memory file like
    node_modules-notes.md are kept, while real vendored dirs inside a skill are
    still skipped.

Validation

  • tsc --noEmit: clean.
  • bun test ./tests/test-*.ts ./tests/test-*.tsx ./tests/unit/*.test.ts:
    4728 pass, 5 skip, 0 fail (235 files).
  • New integration tests (real filesystem, not mocks):
    • skills watcher fires on a real SKILL.md, including prompt-builder /
      build-named skills, and stays quiet for a SKILL.md nested in a vendored
      node_modules/;
    • createMemoryWatcher fires on a top-level .md, filters non-targets, and no
      longer prunes node_modules-notes.md;
    • end-to-end through the real MemoryIndex: a root .md written after startup
      becomes searchable — the exact behavior this fix restores.

Limitations / Follow-ups

  1. The memory watcher now watches the whole workspace root recursively
    (depth: 2) to catch top-level .md; fine for the default
    ~/.hawky/workspace, a minor cost for a large injected workspace.
  2. makeIgnored and the path-prefix check are near-duplicated across
    skills/watcher.ts, memory/watcher.ts, and memory/index.ts — candidate
    for a shared isPathInside / makeSegmentIgnore helper.
  3. The skills watcher still hardcodes ~/.hawky; unifying on the configured root
    is tracked separately as the HAWKY_HOME cleanup.

chokidar v4+ dropped glob support, so the literal glob paths handed to
chokidar (skills/watcher.ts, memory/index.ts) matched nothing: the watchers
registered but never fired. Skill hot-reload was fully dead, and root-level
.md edits never triggered a memory reindex.

Watch the containing directories instead and filter events in the handler:
- skills: watch the skill roots, react only to SKILL.md basenames
- memory: watch the workspace root + sessions dir, filter to indexed files
- replace the unanchored substring ignore regexes (/build/, /dist/, ...)
  with exact path-segment matching scoped below the watch roots, so a skill
  named 'prompt-builder' or a memory file like 'node_modules-notes.md' is no
  longer silently pruned

Add integration tests that write real files and assert the watcher fires, and
that vendored dirs / non-target files stay ignored.
@rich7420
rich7420 force-pushed the fix/dead-file-watchers branch from 92f45ad to 2bd5d17 Compare July 8, 2026 15:51
The chokidar `ignored` function predicate receives a forward-slash-normalized
path on every platform, but makeIgnored built its watch roots with the native
separator and split on it. On Windows the root prefix never matched the
normalized path, so .git/node_modules (and the skills junk dirs) were never
pruned — the watcher over-descended into vendored trees. Fails-open (extra
watches + spurious debounced fires), not a dead watcher, and macOS/Linux were
unaffected since sep is already '/'.

Normalize both the roots and the incoming path to POSIX separators before the
segment match (a no-op on POSIX). Export makeIgnored and add unit tests that
assert vendored-segment pruning, root/skill-folder preservation, and the
substring-vs-segment distinction for both '/' and '\\' inputs.
@rich7420
rich7420 merged commit 5db1b5f into main Jul 9, 2026
3 checks passed
@rich7420
rich7420 deleted the fix/dead-file-watchers branch July 9, 2026 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant