Skip to content

fix(claude): match Claude Code's project-dir slug rule#364

Merged
dpup merged 3 commits into
majorcontext:mainfrom
abezzub-dr:fix/claude-projects-dir-slugify
May 31, 2026
Merged

fix(claude): match Claude Code's project-dir slug rule#364
dpup merged 3 commits into
majorcontext:mainfrom
abezzub-dr:fix/claude-projects-dir-slugify

Conversation

@abezzub-dr
Copy link
Copy Markdown
Contributor

@abezzub-dr abezzub-dr commented May 29, 2026

Problem

WorkspaceToClaudeDir builds the ~/.claude/projects/<dir> name from a workspace path by replacing only / with -. Claude Code's actual rule replaces every non-alphanumeric character with - (verified against the claude binary v2.1.156: letters/digits kept as-is, everything else → a single -, runs not collapsed).

So for any workspace path containing a ., _, or space — e.g. a macOS username like user.name — moat computed a different directory name than the host CLI:

  • host CLI: /Users/user.name/repos/app-Users-user-name-repos-app
  • moat (old): /Users/user.name/repos/app-Users-user.name-repos-app

moat bind-mounts that host directory into the container's ~/.claude/projects/-workspace (internal/run/manager.go), so container sessions and host sessions for the same repo landed in two divergent project dirs. Result: the project's Claude session history and memory store silently forked — moat (container) sessions wrote to one store, host sessions to another.

Fix

Replace the slug logic with a faithful port of Claude Code's rule: keep [A-Za-z0-9], map every other character to a single -, no collapsing of runs. For ASCII paths (all real workspace paths) the output is byte-for-byte identical to Claude Code's. The function doc notes the one non-ASCII edge case (Claude operates on UTF-16 code units; rune iteration differs only for non-ASCII path chars, which don't occur in practice).

Tests

Extended TestWorkspaceToClaudeDir with cases derived empirically from the claude binary: dot/underscore/space → dash, existing dashes preserved, consecutive separators not collapsed, plus a full mixed-character probe string. Every new case failed against the old implementation and passes with the fix. go test -race is green for internal/run/... and internal/providers/claude/....

Note (out of scope here)

This stops the fork going forward but does not migrate already-forked stores: existing session history / memory under the old dot-encoded directory won't move on its own. A one-time migration to merge the old dir into the canonical one is a separate follow-up. The fix also only takes effect once the moat binary is rebuilt/reinstalled.

🤖 Generated with Claude Code

abezzub-dr added a commit to abezzub-dr/moat that referenced this pull request May 29, 2026
WorkspaceToClaudeDir only replaced "/" with "-", but Claude Code
slugifies project directories by replacing every non-alphanumeric
character with "-" (verified against the claude binary v2.1.156).

For any workspace path containing a "." (e.g. a username like
"user.name"), "_", or a space, moat computed a different
~/.claude/projects/ directory name than the host CLI. Because moat
bind-mounts that host directory into the container's
~/.claude/projects/-workspace, container sessions and host sessions
landed in two divergent project dirs — silently forking the project's
session history and memory store.

Replace the slug logic with a faithful port of Claude Code's rule:
keep letters and digits, map everything else to a single "-", with no
collapsing of runs. ASCII paths are byte-for-byte identical to Claude
Code's output.
@abezzub-dr abezzub-dr force-pushed the fix/claude-projects-dir-slugify branch from d5c149b to 834fa7b Compare May 29, 2026 14:59
- Guard the Claude log-sync mount against an empty workspace. An empty
  path slugifies to "" and collapsed filepath.Join to ~/.claude/projects,
  which would bind-mount the host's entire projects tree (every project's
  session history) into the container. Extract claudeProjectsHostDir(),
  which returns "" to skip the mount, and unit-test it. Unreachable today
  (ResolveWorkspacePath always yields an absolute path) but high-impact.
- Drop the dead filepath.ToSlash call: "/", "\" and ":" are all
  non-alphanumeric and already map to "-", so it never changed output.
- Correct the UTF-16 doc note: only characters above U+FFFF (surrogate
  pairs) diverge from Claude Code; BMP non-ASCII maps to one "-" on both
  sides.
@dpup dpup merged commit 7e287bc into majorcontext:main May 31, 2026
4 checks passed
@abezzub-dr abezzub-dr deleted the fix/claude-projects-dir-slugify branch May 31, 2026 17:23
@dpup dpup mentioned this pull request Jun 1, 2026
2 tasks
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.

2 participants