Skip to content

fix(config): use as_posix() for tilde-expanded path_glob (#643 cluster E)#726

Merged
memtomem merged 2 commits intomainfrom
fix/namespace-rule-tilde-posix-glob
May 2, 2026
Merged

fix(config): use as_posix() for tilde-expanded path_glob (#643 cluster E)#726
memtomem merged 2 commits intomainfrom
fix/namespace-rule-tilde-posix-glob

Conversation

@memtomem
Copy link
Copy Markdown
Owner

@memtomem memtomem commented May 2, 2026

Summary

Real production bug, surfaced by Cluster E of the #643 Windows sweep.

NamespacePolicyRule._expand_and_validate_glob (config.py:341-347) stored expanded path_globs via str(Path(v).expanduser()). On Windows that yields backslashes (C:\Users\runneradmin\some\path\**). The downstream consumer is pathspec.GitIgnoreSpec (engine.py:_build_exclude_spec) which interprets gitignore patterns — gitignore syntax is POSIX-only and treats \ as an escape character. A backslash-bearing pattern silently never matches any file on Windows.

Pre-fix Windows behavior

Any namespace rule with a leading ~/ in its glob — a common config shape (~/.claude/projects/**, ~/Documents/notes/**, etc.) — silently failed to match. Files that should have landed in a project-specific namespace fell through to default_namespace, with no warning.

Fix

Path(v).expanduser().as_posix() instead of str(Path(v).expanduser()):

  • Keeps the tilde expansion (still absolute, still platform-correct via Path.home()).
  • Forces / separators in the stored pattern.
  • Matches gitignore semantics on every platform.
  • POSIX no-op for absolute paths.

Test plan

  • macOS: uv run pytest packages/memtomem/tests/test_indexing_engine.py packages/memtomem/tests/test_config_overrides.py packages/memtomem/tests/test_validate_namespace.py339/339 pass.
  • Lint: ruff check + ruff format --check clean.
  • Windows CI: test_home_tilde_in_path_glob_expanded should pass; tilde-prefixed namespace rules should now actually match.

Why production fix (not test fix)

The failing test (test_home_tilde_in_path_glob_expanded) asserted rule.path_glob.endswith("/some/path/**") — this already encoded the POSIX-glob invariant the rule downstream depends on. The test was right; production was lying about the invariant on Windows.

Out of scope

Remaining: A (9 — dirty state, production change), H (2 — misc) = 11 fails after this lands. Cluster B already shipping via parallel work (fix/720-source-filter-windows merged as 88c3e81).

🤖 Generated with Claude Code

pandas-studio and others added 2 commits May 2, 2026 16:45
…r E)

`NamespacePolicyRule._expand_and_validate_glob` stored the expanded
path_glob via `str(Path(v).expanduser())`. On Windows that produces
backslashes (e.g. `C:\Users\runneradmin\some\path\**`). The downstream
consumer is `pathspec.GitIgnoreSpec` (`engine.py:_build_exclude_spec`)
which interprets gitignore patterns: gitignore syntax is POSIX-only and
treats `\` as an escape character, so a backslash-bearing pattern
silently fails to match any file path on Windows.

Net effect on Windows before this change: any namespace rule with a
leading `~/` in its glob (a common config — `~/.claude/projects/**`,
`~/Documents/notes/**`, etc.) silently never matched. Files that should
have landed in a project-specific namespace fell through to
`default_namespace`, with no warning.

`Path(v).expanduser().as_posix()` keeps the tilde expansion (still
absolute, still platform-correct expansion via `Path.home()`) but
forces `/` separators in the stored pattern, matching gitignore
semantics on every platform.

Surfaced by `test_indexing_engine.py::TestNamespacePolicyRules::
test_home_tilde_in_path_glob_expanded` (Cluster E of the #643 sweep) —
the test asserted `rule.path_glob.endswith("/some/path/**")` which
already encoded the POSIX-glob invariant.

macOS: 339/339 pass across `test_indexing_engine.py`,
`test_config_overrides.py`, `test_validate_namespace.py` — no regressions.
On POSIX `as_posix()` is a no-op for absolute paths.

Co-Authored-By: Claude <[email protected]>
Windows CI on the previous commit (7974a26) surfaced a regression in
`test_config_d_namespace_rules_dedup_after_home_expansion`:

  AssertionError: ['C:/Users/runneradmin/some/memtomem-test/**',
                   'C:\\Users\\runneradmin\\some\\memtomem-test\\**']

The validator normalized the `~/` form to forward slashes, but a
config fragment that supplied an already-expanded absolute path went
through unchanged with backslashes. Two fragments declaring the
equivalent rule (one with `~/foo`, one with `C:\Users\me\foo`) then
produced different stored strings, defeating the dedup hash that keys
on `BaseSettings.model_dump`.

Pre-#726 the test passed on Windows by accident: BOTH paths leaked
backslashes (`str(Path("C:\\..."))` is identity), so the strings
matched. The accident was masking the same gitignore-escape bug
described in 7974a26 — neither rule would have actually matched any
file on Windows, so dedup of two non-matching rules was moot.

Now the validator handles both inputs symmetrically:
- `~/...`  → `Path(v).expanduser().as_posix()`
- absolute → `Path(v).as_posix()`
- relative → unchanged (gitignore patterns can legitimately use `\` as
  an escape character in non-path contexts; only normalize when the
  input is structurally a path)

Test fix: `abs_form` is built via `as_posix()` so the equality check on
the deduped rule's stored value works on Windows. POSIX semantics are
unchanged.

macOS: 339/339 still pass.

Co-Authored-By: Claude <[email protected]>
@memtomem
Copy link
Copy Markdown
Owner Author

memtomem commented May 2, 2026

Heads-up: pushed a follow-up commit b815684 to address a Windows CI regression surfaced by the previous commit 7974a26.

test_config_d_namespace_rules_dedup_after_home_expansion failed on Windows because the validator normalized only the ~/ form — a fragment that supplied an already-expanded absolute path (C:\Users\foo\**) went through unchanged with backslashes, defeating dedup against the normalized ~/ form (C:/Users/foo/**).

The follow-up symmetrizes the normalization:

  • ~/...Path(v).expanduser().as_posix() (existing)
  • is_absolute()Path(v).as_posix() (new)
  • relative patterns → unchanged (gitignore escapes can legitimately use \ in non-path contexts)

Pre-#726 the test passed on Windows by accident — both paths leaked backslashes and matched textually, while neither would have actually matched any file due to the gitignore-escape bug 7974a26 documents. Dedup of two non-matching rules was moot.

Test updated: abs_form now built via as_posix() to assert against the new (correct) stored form. 339/339 macOS pass.

@memtomem memtomem merged commit b72197d into main May 2, 2026
8 of 9 checks passed
@memtomem memtomem deleted the fix/namespace-rule-tilde-posix-glob branch May 2, 2026 10:10
@github-actions github-actions Bot locked and limited conversation to collaborators May 2, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants