Skip to content

Windows: CBM_CACHE_DIR / CBM_ALLOWED_ROOT are lossily decoded — every non-ASCII char becomes U+FFFD, so the server silently uses a different path (v0.9.0) #1165

Open

Description

@sowa-content

Windows: CBM_CACHE_DIR / CBM_ALLOWED_ROOT are lossily decoded — every non-ASCII char becomes U+FFFD, so the server silently uses a different path (v0.9.0)

Summary

On Windows v0.9.0, the values of CBM_CACHE_DIR and CBM_ALLOWED_ROOT are decoded
lossily: each non-ASCII character is replaced with U+FFFD (REPLACEMENT CHARACTER).
The server then operates on that mangled path instead of the one you set.

One defect, three very different-looking symptoms:

Symptom Underlying cause
config set reports success but nothing persists DB written to the U+FFFD twin directory
error: cannot open config database mangled path is not creatable (e.g. under C:\Users\)
every index_repository fails, even in-root mangled allowed-root never matches the real repo path

CLI arguments are not affected — --repo-path with the same characters works
correctly and encodes them properly into the project name (ö → c3b6). The bug is
specific to reading these environment variables.

This is distinct from #903 / #571, which concern the repository path (there indexing
succeeds and only retrieval/naming degrades).

Environment

  • codebase-memory-mcp v0.9.0, codebase-memory-mcp-windows-amd64.zip (standard variant)
  • Windows 11 Pro 10.0.26200, AMD64
  • Reproduced identically from both PowerShell 5.1 and Git Bash — not a shell-quoting artifact

Proof: the tool creates a mojibake twin directory

Set the cache dir to a path with non-ASCII characters, then look at what appeared on disk:

$env:CBM_CACHE_DIR = "C:\Tëst-Ünïcode\cache"
codebase-memory-mcp config set auto_watch false     # → "auto_watch = false"
codebase-memory-mcp config list                     # → auto_watch = false

Both commands report success. But C:\Tëst-Ünïcode\cache is never created. Instead:

Get-ChildItem C:\ -Directory | Where-Object Name -like "T*st*code" |
  ForEach-Object { ($_.Name.ToCharArray() | % { "U+{0:X4}" -f [int]$_ }) -join " " }

# the directory I created:
Tëst-Ünïcode  ->  U+0054 U+00EB U+0073 U+0074 U+002D U+00DC U+006E U+00EF U+0063 U+006F U+0064 U+0065
# the directory the tool created:
T?st-?n?code  ->  U+0054 U+FFFD U+0073 U+0074 U+002D U+FFFD U+006E U+FFFD U+0063 U+006F U+0064 U+0065

Every non-ASCII codepoint has been replaced by U+FFFD. _config.db lands in the twin
directory, so settings appear to save and read back correctly while the configured
location stays empty.

Symptom 2: hard failure when the mangled path is not creatable

$env:CBM_CACHE_DIR = "C:\Users\SomeÜser\cache"   # any profile with a non-ASCII name
codebase-memory-mcp config list
# → error: cannot open config database

Here the twin path would be C:\Users\SomeU+FFFDser\cache, and creating a new directory
directly under C:\Users\ is not permitted, so it fails outright instead of silently
diverging. Same physical directory reached through its ASCII 8.3 alias works fine:

$env:CBM_CACHE_DIR = "C:\Users\SOMEUS~1\cache"
codebase-memory-mcp config list   # → prints config, _config.db created

This makes config get/set/list unusable on such an account — including turning
auto_watch / auto_index off.

Symptom 3: CBM_ALLOWED_ROOT rejects everything, including valid in-root paths

$env:CBM_CACHE_DIR    = "C:\cbm-data"          # ASCII, fine
$env:CBM_ALLOWED_ROOT = "C:\Tëst-Ünïcode"
codebase-memory-mcp cli index_repository --repo-path "C:\Tëst-Ünïcode\repro"
# → {"status":"error","outcome":"exit_nonzero",
#    "hint":"Indexing worker crashed on a file. The crash was contained ..."}

The repo is inside the allowed root and must be accepted. The mangled root can never
match the real path, so the guard refuses everything. Control — same repo, variable unset:

Remove-Item Env:\CBM_ALLOWED_ROOT
codebase-memory-mcp cli index_repository --repo-path "C:\Tëst-Ünïcode\repro"
# → {"project":"C-Tc3abst-c39cnc3afcode-repro","nodes":7,"edges":7,"status":"indexed"}

Note the project name here encodes the characters correctly (ë→c3ab, Ü→c39c,
ï→c3af) — further evidence that only the env-var read path is broken.

Impact

Any Windows account whose name contains a non-ASCII character — common in German,
French, Nordic, Slavic, Turkish and CJK locales — cannot use CBM_ALLOWED_ROOT at all,
and the default cache dir under $HOME is unusable.

CBM_ALLOWED_ROOT is the documented isolation control for "agentic or multi-tenant
deployments". Losing it is bad; losing it silently is worse. Note the two failure
modes are asymmetric in danger: the hard error is loud, but the U+FFFD-twin case looks
like everything worked.

Diagnostics point away from the cause

  • cannot open config database reads like a permissions or file-lock problem.
  • Indexing worker crashed on a file. ... a future release isolates the culprit file
    actively blames a source file. No file is at fault, and the worker log written to
    <cache>/logs/.worker-<pid>.log is 0 bytes, so there is nothing to debug from.

Independent of the encoding fix, two cheap improvements would have saved hours here:

  1. Validate env-var paths at startup and fail with a message naming the variable and the
    resolved path. If the resolved path differs from the input, that alone is the bug.
  2. Make the worker actually write its failure reason into its log file.

Workaround

Point every path at an ASCII location; a junction avoids moving anything:

New-Item -ItemType Junction -Path C:\myrepo -Target C:\Users\SomeÜser\myrepo
$env:CBM_ALLOWED_ROOT = "C:\myrepo"
$env:CBM_CACHE_DIR    = "C:\cbm-data"

Also keep the binary itself on an ASCII path: when it lives under a non-ASCII directory
the supervisor cannot spawn workers and logs
index.supervisor.spawn_failed action=degrade_in_process, silently degrading to
in-process indexing.

Related: #903 (non-ASCII repo path → retrieval fails), #571 (project name strips non-ASCII).

Activity

  1. added this to the 0.9.1-rc milestone on Jul 18, 2026
  2. added
    priority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.
    on Jul 18, 2026
  3. DeusData commented on Jul 18, 2026

    @DeusData
    Owner

    Thanks for the unusually thorough byte-level diagnosis and controls. This is a high-priority 0.9.1-rc Windows correctness and containment bug under #394. The RED coverage should read both environment variables through the Windows wide-character path, prove the exact non-ASCII path reaches cache/config operations, prove the allowed-root comparison uses the same canonical identity as the repository path, and verify the supervisor can spawn from a non-ASCII executable path. The zero-byte worker diagnostic is also relevant to #1132, but does not replace these encoding-specific guards.

  4. sowa-content commented on Jul 18, 2026

    @sowa-content
    Author

    Thanks — that coverage list matches what I saw, with one gap worth adding.

    The zero-config case is affected too. All my repros set the variables explicitly, but the default cache path derived from $HOME hits the same decode. With both variables unset on a profile whose name contains a non-ASCII character:

    codebase-memory-mcp config list
    # → error: cannot open config database
    

    ~/.cache/codebase-memory-mcp exists and stays empty — nothing is ever written there. So a fresh install on such an account is broken before the user configures anything; CBM_CACHE_DIR was how I worked around it, not how I hit it. Suggest the RED coverage include a no-env-vars case with a non-ASCII $HOME, since that is the path most users will actually take.

    The asymmetry between the two failure modes may also be worth encoding as two separate assertions: when the mangled path happens to be creatable, the tool reports success and writes to a U+FFFD twin directory (silent), and only fails loudly when the twin cannot be created — e.g. a new directory directly under C:\Users\. A test that only asserts "returns an error" would pass while the silent-divergence case still ships.

    Happy to run a 0.9.1-rc build against a real non-ASCII Windows profile and report back — that is the environment I have.

  5. DeusData commented on Jul 19, 2026

    @DeusData
    Owner

    Thanks, the zero-configuration $HOME case is important. The regression coverage needs to include non-ASCII Windows profiles with both path variables unset, plus separate assertions for silent U+FFFD twin-directory creation and hard path-open failure. This is back in the maintainer queue; no further reporter action is needed right now.

  6. DeusData commented on Sep 20, 2026

    @DeusData
    Owner

    A status update, because this issue is more fixed than its open state suggests — and you offered a real non-ASCII profile to test on, which is exactly what is needed to finish it.

    What landed, both in v0.10.0:

    • faf5160 — environment variables are read wide on Windows (GetEnvironmentVariableW, converted to UTF-8). CBM_CACHE_DIR goes through that path, and so does the zero-config default derived from USERPROFILE; the config database is opened on the UTF-8 path and its directory is created with _wmkdir.
    • 0e00ef5 — CBM_ALLOWED_ROOT is read wide and canonicalised once per CLI and daemon session; index workers are scoped explicitly and never fall back to the environment.

    tests/windows/test_non_ascii_cache_dump.py guards a non-ASCII CBM_CACHE_DIR end to end (index plus read-back) on the Windows CI leg.

    What is not done, so I am not closing this yet: a handful of fallback reads still use the narrow getenv (the UI's index route for CBM_ALLOWED_ROOT, the MCP fallback used only when no session policy is set, the home-directory helper, and a save/restore around activation), and three guards I would want before calling it finished do not exist yet: a non-ASCII CBM_ALLOWED_ROOT, config set / config list under a non-ASCII cache directory, and a non-ASCII profile with both variables unset.

    If you have ten minutes on v0.11.0, the most useful check is exactly your original setup: both variables pointing at a path with non-ASCII characters, then codebase-memory-mcp config set <key> <value> followed by config list, and one index_repository on a repository under the allowed root. A plain "works" or the exact error text settles whether the remaining fallback reads matter in practice. Either way the residual narrow reads get their own small follow-up.

    Thank you for the original report — the lossy-decoding diagnosis in it was correct, and it is what the fix was built on.

  7. DeusData commented on Oct 19, 2026

    @DeusData
    Owner

    Thank you for adding the zero-configuration non-ASCII profile case. The last update explicitly left fallback reads and several coverage gaps open; I have no new verification to add. Your config round-trip and allowed-root checks remain useful, without replacing those maintainer-side follow-ups.

  8. RithvikReddy0-0 commented on Oct 23, 2026

    @RithvikReddy0-0
    Contributor

    Retested on v0.12.1 (official codebase-memory-mcp-windows-amd64.zip, SHA-256 matches checksums.txt), Windows 11 build 26200, PowerShell 5.1. I couldn't reproduce any of the original symptoms. Each case ran with an accented path (Tëst-Ünïcode) and a CJK path (測試):

    Case Result
    Non-ASCII CBM_CACHE_DIR, config set auto_watch false then config list works; _config.db written to the real directory, no U+FFFD twin created
    Non-ASCII CBM_ALLOWED_ROOT, index_repository on a repo inside it indexed; list_projects shows the correct non-ASCII root_path
    Same allowed root, repo outside it refused ("outside the allowed root")
    Both variables unset, non-ASCII HOME + USERPROFILE config round-trips; .cache/codebase-memory-mcp/_config.db and the daemon runtime dir land under the non-ASCII profile
    Both unset, USERPROFILE only (HOME unset) same

    Caveat: my account name is ASCII. I simulated the non-ASCII profile by pointing HOME/USERPROFILE at a non-ASCII directory, so a real non-ASCII account (as @sowa-content offered) would still be the stronger check.

    Not covered: the remaining narrow getenv fallbacks you listed (src/ui/http_server.c:1715, the three mcp.c fallbacks when no session policy is set, and the CBM_CACHE_DIR save/restore in cli.c:811). I didn't exercise the UI route or the no-policy MCP path.

    Two small things I noticed while testing, happy to open separate issues if useful:

    • On PowerShell 5.1, the raw-JSON form cli index_repository '{"repo_path":"..."}' fails with repo_path is required because PS 5.1 strips the embedded quotes; --repo-path works.
    • The refusal hint prints run: codebase-memory-mcp allow-root C:/Users/<name with space>/... without quoting the path, so copying it breaks for any path containing a space.
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

    bugSomething isn't workingpriority/highNeeds near-term maintainer attention; high-impact bug, regression, safety issue, or release blocker.stability/performanceServer crashes, OOM, hangs, high CPU/memoryux/behaviorDisplay bugs, docs, adoption UXwindowsWindows-specific issues

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions