Skip to content

fix(paths): expand ~ in CLAUDE_MEM_DATA_DIR (stray ~ dirs)#3350

Open
quinnmacro wants to merge 1 commit into
thedotmack:mainfrom
quinnmacro:fix/expand-tilde-data-dir
Open

fix(paths): expand ~ in CLAUDE_MEM_DATA_DIR (stray ~ dirs)#3350
quinnmacro wants to merge 1 commit into
thedotmack:mainfrom
quinnmacro:fix/expand-tilde-data-dir

Conversation

@quinnmacro

@quinnmacro quinnmacro commented Jul 21, 2026

Copy link
Copy Markdown

Problem

A literal ~-prefixed CLAUDE_MEM_DATA_DIR (e.g. ~/.claude-mem written into settings.json, or set as an env var from a non-shell context) produced stray directories literally named ~ across the workspace.

resolveDataDir() returned the value verbatim from both the env var and settings.json, without tilde expansion. Node's path.join / fs do not expand ~ (only the shell does), so the value was treated as a relative path and resolved against the process cwd. claude-mem workers inherit the cwd of whatever spawned them — a subagent pinned to a subdirectory, a plugin-install dir, the repo root, an Obsidian plugin folder — so each worker created its own cwd/~/.claude-mem/ tree (logs + a settings.json snapshot), e.g.:

<repo>/~/.claude-mem/logs/...
<repo>/<subdir>/~/.claude-mem/logs/...
<repo>/<subdir>/<nested-dir>/~/.claude-mem/logs/...
<repo>/<plugin-dir>/~/.claude-mem/...

The default value (join(homedir(), '.claude-mem')) was already absolute, so this only bit users whose settings.json literally contained "~/.claude-mem" (hand-edited configs, or configs written by tooling that didn't expand ~).

Fix

Add expandHome() in src/shared/paths.ts and route both return paths of resolveDataDir() through it:

  • ~/foojoin(homedir(), 'foo')
  • bare ~homedir()
  • absolute paths → untouched
  • relative paths without ~ → untouched
  • ~user/... → intentionally untouched (resolving another user's home is out of scope and platform-dependent)

This keeps every downstream path (LOGS_DIR, DB_PATH, paths.*) absolute regardless of how the value was authored, without changing behavior for the (already-absolute) default.

Verification

  • bun test tests/shared/paths.test.ts → 12 pass (6 new expandHome cases + 3 resolveDataDir tilde cases + 3 existing)
  • bun test tests/shared/ → 130 pass, 0 fail
  • tsc --noEmit → 0 errors

The resolveDataDir tests exercise the env-var branch (consulted first) so they don't touch the real ~/.claude-mem/settings.json on disk.

Workaround already in place locally

Beyond this source fix, the equivalent defense without a code change is to set CLAUDE_MEM_DATA_DIR to an absolute path in the Claude Code env block (~/.claude/settings.json) so te()/resolveDataDir() hits the env-var branch first — useful for anyone who can't upgrade immediately.

🤖 Generated with Claude Code

… stray `~` dirs

`resolveDataDir()` returned the `CLAUDE_MEM_DATA_DIR` value verbatim from
both the env var and settings.json, without tilde expansion. Node's
`path.join` / `fs` do not expand `~` (only the shell does), so a value
written as the literal `~/.claude-mem` was treated as a *relative* path
and resolved against the process cwd. claude-mem workers inherit the cwd
of whatever spawned them (subagents pinned to a subdirectory, a plugin
install dir, etc.), so a `~`-prefixed DATA_DIR produced stray
`~/.claude-mem/` trees — a directory literally named `~` — wherever the
worker happened to be running.

Add `expandHome()` and route both return paths through it. Absolute and
non-`~` values are untouched; `~/` and a bare `~` resolve to `homedir()`;
the `~user/` form is intentionally left alone (out of scope, platform-
dependent).

Verified: 12 paths tests pass (6 new for expandHome, 3 for resolveDataDir
tilde handling), 130 shared tests pass, `tsc --noEmit` clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@greptile-apps

greptile-apps Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR expands ~ in configured claude-mem data directories. The main changes are:

  • Adds expandHome() for bare ~ and leading ~/ paths.
  • Applies expansion to CLAUDE_MEM_DATA_DIR from both the environment and settings.json.
  • Adds tests for tilde expansion and resolveDataDir() env-var behavior.

Confidence Score: 5/5

Safe to merge with minimal risk.

The change is narrow, preserves existing behavior for absolute and non-tilde relative paths, and adds tests for the affected path-resolution behavior.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • The focused path tests were run and passed with exit code 0, as shown in the log shared-paths-01-focused.log.
  • The broader shared tests were run and passed with exit code 0, as shown in the log shared-paths-02-shared-suite.log.
  • The root typecheck completed successfully with exit code 0, as shown in the log shared-paths-03-typecheck-root.log.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
src/shared/paths.ts Adds expandHome() and applies it to data-dir values from the environment and settings before downstream paths are derived.
tests/shared/paths.test.ts Adds focused coverage for tilde expansion and resolveDataDir() behavior through the environment-variable branch.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
  participant Env as CLAUDE_MEM_DATA_DIR env
  participant Settings as settings.json
  participant Resolver as resolveDataDir()
  participant Expander as expandHome()
  participant Paths as DATA_DIR-derived paths

  Resolver->>Env: Check env var first
  alt env var is set
    Env-->>Resolver: data-dir value
    Resolver->>Expander: normalize leading ~
  else env var is unset
    Resolver->>Settings: Read default settings file
    alt settings contains CLAUDE_MEM_DATA_DIR
      Settings-->>Resolver: data-dir value
      Resolver->>Expander: normalize leading ~
    else no configured value
      Resolver-->>Paths: homedir()/.claude-mem default
    end
  end
  Expander-->>Resolver: expanded or unchanged path
  Resolver-->>Paths: DATA_DIR for logs/db/settings accessors
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
  participant Env as CLAUDE_MEM_DATA_DIR env
  participant Settings as settings.json
  participant Resolver as resolveDataDir()
  participant Expander as expandHome()
  participant Paths as DATA_DIR-derived paths

  Resolver->>Env: Check env var first
  alt env var is set
    Env-->>Resolver: data-dir value
    Resolver->>Expander: normalize leading ~
  else env var is unset
    Resolver->>Settings: Read default settings file
    alt settings contains CLAUDE_MEM_DATA_DIR
      Settings-->>Resolver: data-dir value
      Resolver->>Expander: normalize leading ~
    else no configured value
      Resolver-->>Paths: homedir()/.claude-mem default
    end
  end
  Expander-->>Resolver: expanded or unchanged path
  Resolver-->>Paths: DATA_DIR for logs/db/settings accessors
Loading

Reviews (1): Last reviewed commit: "fix(paths): expand `~` in CLAUDE_MEM_DAT..." | Re-trigger Greptile

@quinnmacro

Copy link
Copy Markdown
Author

Hi 👋 — gentle follow-up on this one.

This came from a real production hit: a ~-prefixed CLAUDE_MEM_DATA_DIR (written literally into settings.json) produced stray directories named ~ scattered across the workspace wherever a worker's cwd happened to be (subagent-pinned subdirs, plugin-install dirs, the repo root). The fix is narrow — route both resolveDataDir() return paths through a new expandHome() that only touches leading ~/ and bare ~; absolute and non-tilde paths are byte-for-byte unchanged.

Greptile's T‑Rex already cloned the branch and ran the tests green (focused path tests, the shared suite, and tsc --noEmit — all exit 0), so the verification is already on the PR.

Happy to squash, rebase onto a newer main, split the test additions into a separate commit, or adjust the ~user/ scope decision if you'd prefer different behavior. Just let me know.

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