Skip to content

[BUG] .bmignore cannot express any pattern beginning with #, despite documenting gitignore-style syntax #1539

Description

@tsobczynski

Bug Description

.bmignore is documented as using "standard gitignore-style syntax" — both in create_default_bmignore() and in the header of the file it generates — but the loader is a naive line filter, so no pattern beginning with # can ever take effect.

load_bmignore_patterns() in basic_memory/ignore_utils.py:

for line in f:
    line = line.strip()
    # Skip empty lines and comments
    if line and not line.startswith("#"):
        patterns.add(line)

There is no backslash unescaping anywhere, so:

  • #*# is silently dropped as a comment.
  • \#*# is loaded literally, then matched with fnmatch, which has no escape syntax — so it matches nothing.

Real gitignore covers exactly this case: "Put a backslash (\) in front of the first hash for patterns that begin with a hash."

Why it matters

This makes it impossible to ignore Emacs autosave files (#file#) — which is precisely what we needed, after an autosave corrupted a note's index row (#1537).

The failure mode is silent and actively misleading: the pattern looks correct sitting in .bmignore, and simply never loads. The shipped defaults already cover *~, *.swp and *.swo, so editor scratch files are clearly meant to be in scope — #file# is the gap, and it is the one that cannot be expressed.

Steps To Reproduce

Verified against the shipped functions, not from reading the code:

>>> from basic_memory.ignore_utils import load_bmignore_patterns, should_ignore_path
>>> # .bmignore containing: '# comment', '.*', '*~', '#*#', '*#'
>>> sorted(load_bmignore_patterns())
['*#', '*~', '.*']                      # '#*#' silently dropped

>>> base = Path('/path/to/project')
>>> should_ignore_path(base/'notes/#note.md#', base, {'#*#'})
True
>>> should_ignore_path(base/'notes/#note.md#', base, {'\\#*#'})
False
>>> should_ignore_path(base/'notes/#note.md#', base, {'*#'})
True

Expected Behavior

Either honor the gitignore escape (\#), or stop describing the format as gitignore-style and document which subset is actually supported.

Actual Behavior

#*# never loads; \#*# loads but never matches. Note that should_ignore_path() itself handles #*# correctly — it fnmatches each path part individually, so the matcher is fine and only the loader is broken. That asymmetry is what makes this hard to diagnose from the outside.

Possible Solution

  1. In the loader, strip a single leading backslash after the comment check, so \#*# yields the pattern #*#. The matcher already handles it.
  2. Add an Emacs autosave pattern to DEFAULT_IGNORE_PATTERNS, alongside *~ and *.swp.
  3. Or adopt pathspec (or similar) for genuine gitignore semantics, and keep the documented claim accurate.

Workaround for anyone hitting this: use *#, which survives the loader and matches per-path-part. It has no false positives in a Markdown project, since no legitimate note name ends in #. Confirmed live — a probe #probe.md# produced no index row and was not even logged, and the pattern took effect in already-running servers with no restart.

Environment

  • OS: macOS 26.6.2 (Darwin 25.6.0), arm64
  • Python: 3.14.7
  • basic-memory: 0.23.2
  • Install method: pipenv virtualenv; MCP servers launched by Claude Desktop and Claude Code
  • Config: local SQLite, semantic_search_enabled: true, index_changes: true, kebab_filenames: false

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions