fix(desktop): self-heal project re-add roots#6158
Merged
SivanCola merged 5 commits intoJul 7, 2026
Merged
Conversation
added 5 commits
July 8, 2026 01:44
… Windows contract The registry-side matcher still left same-shape exact comparisons on paths that cross spelling sources: - fold case in topicSummaryKey (matching agent.CanonicalSessionPath's key form) so sidebar topic runtime status never splits across the tab, registry, and session-meta root spellings - match roots with sameProjectRoot in blank-tab reuse, open-topic collection, session-binding workspace notices, topic prepend/repair, indexed blank-topic reuse, and ListWorkspaces' Current flag - keep the global sidebar token out of path comparisons in normalizeSidebarOrder The re-add regression tests spelled the equivalent root with filepath.Join(root, "."), which Join cleans back to the identical string, so they also passed on the unpatched base. Spell the dot segment by hand, and add Windows-only case-folding tests (*OnWindows) plus a windows-latest desktop CI leg to run them - the ubuntu leg can never reach the EqualFold branch.
…b root spellings WorkspaceSlug fed the raw absolute path into the slug, so on Windows C:\Foo and c:\foo produced two projects/<slug> state directories and the same workspace's session list and memory-compiler state split. Fold case there (matching agent.CanonicalSessionPath's key form) and route internal/memory's duplicate slugify through it. Existing mixed-case slug directories keep working: NTFS resolves directory names case-insensitively, so the folded slug opens the same directory and no migration is needed. Unix slugs are unchanged - case-different paths there are genuinely different directories. The frontend compares tab.workspaceRoot against tree node roots with exact string equality, so a registry re-spell (addProject adopting the caller's spelling) could strand open tabs on the old spelling and break ownership highlighting and session filters. Sync open project tabs to the registry's canonical spelling after every registry write (registerProjectRoot / rename / color paths), persist the tab snapshot, and emit project-tree:changed so the frontend refreshes - keeping one string form as the single source of truth instead of teaching the frontend platform-aware path semantics. Also fold the startup-migration and recovery-GC seen maps (desktopMCPMigrationRoots, recoverLegacyProjectSidebarRoots, recoveryGCDirs) with projectRootKey so case-split roots are not scanned or appended twice, and cover the tab re-spell sync with a Windows-only regression test on the desktop-windows CI leg.
config.WorkspaceSlug now folds case on Windows so equivalent spellings of one workspace share a slug; TestStoreForSlug asserted the unfolded spelling and failed on the windows-latest leg.
…meProjectRoot Five project-root comparisons still used normalizeProjectRoot(a) == normalizeProjectRoot(b), which does not fold case on Windows — so a re-add under a different case spelling could miss the existing session/tab/migration match: - topicSessionMatchMatchesTarget (session meta vs registry root) - tabMatchesTopicTarget (tab reuse for same topic) - tabInWorkspace (tab-in-workspace predicate) - reconcileTabWithLiveBinding (controller root vs tab root guard) - legacySessionScopeMatchesMigrationTarget (migration scope check) Switch all five to sameProjectRoot and add a Windows-only regression test that plants a session with the original-case root in its meta, re-registers the project under a flipped-case root, and verifies findTopicSessionForTarget still resolves the original session.
3cde774 to
824f232
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.
Summary
ListWorkspaces'CurrentflagtopicSummaryKey(same key form asagent.CanonicalSessionPath) so sidebar topic runtime status never splits across the tab, registry, and session-meta root spellingsconfig.WorkspaceSlugso equivalent spellings of one workspace no longer split itsprojects/<slug>session/memory state directories;internal/memory's duplicateslugifynow routes through it. No migration needed: NTFS resolves directory names case-insensitively, so the folded slug opens the same directoryregisterProjectRoot/ rename / color), persist the tab snapshot, and emitproject-tree:changed— the frontend keeps comparing plain strings against a single source of truth instead of learning platform path semanticsdesktopMCPMigrationRoots,recoverLegacyProjectSidebarRoots,recoveryGCDirs) withprojectRootKeyso case-split roots are not scanned or appended twicenormalizeSidebarOrder*OnWindows) including tab re-spell sync,WorkspaceSlugfold/preserve tests, plus awindows-latestdesktop CI leg to run the Windows-only testsRoot Cause
The desktop project registry used exact string equality for several root-path operations. If an existing record used an equivalent spelling of the same folder, later remove/re-add, pin, or reorder operations could miss the stale entry instead of repairing it. On Linux/macOS
normalizeProjectRootalready collapses dot segments and trailing separators, so the reachable spelling split is Windows case differences (filepath.Absdoes not fold case) — which is why the contract tests are Windows-only and need the new CI leg. The same key-splitting shape also affectedconfig.WorkspaceSlug(persistent per-project state directories) and the frontend's exactworkspaceRootcomparisons, both fixed here at the owner level.Known limitations
sameDesktopPathonly folds case on Windows, so same-folder case variants can still split on macOS. This matches the existingsameDesktopPathsemantics./varvs/private/var) are not treated as equivalent; nothing resolves symlinks today.Checks
go test ./...(root module, includes theWorkspaceSlugfold/preserve tests)cd desktop && go test .cd desktop && go test . -run 'OnWindows$' -v(runs on the newdesktop-windowsCI leg; skips elsewhere)gofmt -l/go vetclean on both modulesgit diff --checkCache
Cache-impact: none - internal/memory/store.go only routes the project-slug computation through config.WorkspaceSlug (deduping a copy of slugify). The slug names the on-disk memory storage directory; it is never emitted into the provider-visible system prompt, and off Windows the produced bytes are identical to before. The MEMORY.md index text that loads into the cached prefix is unchanged.
Cache-guard: ./scripts/cache-guard.sh (TestReleaseCacheHitGuard) passed; no prompt-construction, tool-schema, or memory-index-format bytes changed.
System-prompt-review: No system-prompt surface touched - the change is a storage-path helper dedup, so the cached system-prompt prefix is byte-identical on all platforms except the Windows storage directory casing.