One project, however its path is spelled - #259
Merged
Merged
Conversation
Creating a child loop from inside a codespace added a second project to the sidebar and put the loop in it, where nothing was watching. Opening a project is create-if-missing — that is how `graphcode status <folder>` adds a folder from a shell — and every path a client named went straight through it. A loop names paths constantly: its own worktree, its working directory, its project with a trailing slash. Each became another project with its own graph, its own recents entry and its own sidebar row under the same name. A codespace turned that from possible into certain. A remote path cannot be checked against this filesystem, so unlike a local folder *every* spelling of one was openable: `codespace://cs/workspaces/repo/`, `…//repo`, and every subpath of it were four projects. Three changes, from the daemon outwards: - `canonicalize` now normalizes a remote path's spelling the way it has always normalized a local one. Trailing slashes, doubled separators and `.` segments collapse; different hosts stay different projects. - Routing a named path is now a policy rather than a lookup. For a client that is not the app, a folder inside a project that already exists *is* that project, and a remote path the daemon has never seen is refused — remote projects are added in the app, which validates the connection over ssh first. The app keeps both powers: opening a nested folder and adding a host are deliberate human acts there. - A refusal is now sent back as an error instead of dropped. Naming a path that was not a project used to be silence: the CLI waited out its socket timeout and then reported the command as *possibly applied*, when nothing past the open had been sent. Both CLIs read it and print the one line saying which path was wrong. The remote shim resolves before it opens. It is the one place that knows a bare absolute path is *this host's* spelling, so `/workspaces/repo` and a worktree under it now address the codespace project they belong to, and say so on stderr. Sidebar rows a pre-fix daemon left behind clear themselves out on the next launch, but only the empty ones: a twin that collected loops stays exactly where it is, because those loops are somebody's work and a merged-away row is one nobody can find again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WYzCYpAW75pcCCDD1eq7wV
scgopi
force-pushed
the
fix/duplicate-project-paths
branch
from
September 3, 2026 03:04
24075fc to
e0a0043
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was happening
Creating a child loop from inside a codespace added a second project to the sidebar — same name, empty graph — and the child loop went into it, where nothing was watching.
Why
Opening a project is create-if-missing. That is deliberate:
graphcode status <folder>from a shell is how a folder gets added. But every path a client named went straight through it, and a loop names paths constantly — its own worktree, its working directory, its project path with a trailing slash.A codespace turned that from possible into certain. A remote path cannot be checked against this filesystem, so unlike a local folder, every spelling of one was openable. Reproduced against a real daemon before the fix:
Locally the same bug is bounded by the existence check: only a real subfolder or worktree of a project could become a second project (
graphcode status /repo/docsadded "docs").Naming a path that could not be opened was worse than useless: the refusal was dropped, the CLI waited out its socket timeout, and then reported the command as possibly applied when nothing past the open had been sent.
The fix
canonicalizenormalizes remote spellings the way it has always normalized local ones. Trailing slashes, doubled separators and.segments collapse. Different hosts stay different projects./workspaces/repoand worktrees under it address the codespace project they belong to, and it says so on stderr.Verified
After the fix, against a live daemon with one codespace project added the way the app adds it:
status codespace://cs/workspaces/widget/node create codespace://cs/workspaces/widget/worktrees/fix-215 …status codespace://other-space/workspaces/widgetgraphcode projectsstatus /workspaces/widget(Mac shell)status /workspaces/widget(shim, on the host)status /brand/new/folderTest run: 1518 tests, 157 suites, all passing (10 new, in
DuplicateProjectPathTestsandRemoteCLIShimTests). swiftlint clean, swift-format clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01WYzCYpAW75pcCCDD1eq7wV