Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion server/projects.js
Original file line number Diff line number Diff line change
Expand Up @@ -992,7 +992,7 @@ async function addProjectManually(projectPath, displayName = null) {
}

// Generate project name (encode path for use as directory name)
const projectName = absolutePath.replace(/\//g, '-');
const projectName = absolutePath.replace(/[^a-zA-Z0-9]/g, '-');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Name collision risk and broken decoding logic.

This change introduces two critical issues:

  1. Name collisions: Different project paths can now map to the same projectName, causing conflicts. For example:

    • /workspace/my_project-workspace-my-project
    • /workspace/my-project-workspace-my-project

    The collision check at line 1001 would incorrectly reject the second (legitimately different) project.

  2. Broken fallback decoding: Multiple locations in the codebase decode projectName back to paths using replace(/-/g, '/'), assuming the old encoding scheme where only slashes were replaced:

    • Line 237 in generateDisplayName
    • Lines 286, 323, 347, 360, 364 in extractProjectDirectory
    • Line 469 in getProjects

    With the new encoding, this decoding produces incorrect paths. For example, if projectName is -workspace-my-project-version-1-0, decoding yields /workspace/my/project/version/1/0 instead of the original path.

While originalPath is stored for newly added manual projects (line 1011), the fallback logic will fail when originalPath is unavailable or for projects encoded with the old scheme.

Recommended solutions:

  1. Maintain bidirectional mapping: Store a mapping of encoded names to original paths in the config, or use a reversible encoding scheme (e.g., URL encoding or base64).

  2. Update all decoding sites: Replace all instances of projectName.replace(/-/g, '/') with logic that retrieves the path from config's originalPath field.

  3. Prevent collisions: Before adding a project, check if any existing config entry has a different originalPath but the same encoded projectName.

  4. Migration strategy: Consider how existing projects with the old encoding will coexist with new ones, or implement a one-time migration to re-encode all existing projects.

Example diff for collision prevention:

  // Check if project already exists in config
  const config = await loadProjectConfig();
  const projectDir = path.join(process.env.HOME, '.claude', 'projects', projectName);

  if (config[projectName]) {
-    throw new Error(`Project already configured for path: ${absolutePath}`);
+    // Check if it's truly the same project or a collision
+    if (config[projectName].originalPath !== absolutePath) {
+      throw new Error(`Project name collision: A different project at '${config[projectName].originalPath}' already uses the encoded name '${projectName}'`);
+    }
+    throw new Error(`Project already configured for path: ${absolutePath}`);
  }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In server/projects.js around line 995, the new projectName generation (replacing
all non-alphanumerics with '-') causes name collisions and breaks existing
decoding that expects '-'→'/' replacements; fix by switching to a reversible
encoding or storing the original path: update project creation to save an
originalPath (or use base64/encodeURIComponent for projectName) in the config,
change all decoding sites to prefer config.originalPath when present (falling
back to any legacy decoding only if originalPath is missing), add a pre-add
collision check that looks up any existing entry with the same projectName but a
different originalPath and reject/rename accordingly, and plan a one-time
migration or compatibility logic to re-encode or populate originalPath for
legacy entries so old and new encodings don’t conflict.


// Check if project already exists in config
const config = await loadProjectConfig();
Expand Down