Skip to content

fix: smart uninstaller cleanup + prevent nested .ragcode dirs#48

Open
doITmagic wants to merge 1 commit intodevfrom
fix/uninstaller-smart-cleanup
Open

fix: smart uninstaller cleanup + prevent nested .ragcode dirs#48
doITmagic wants to merge 1 commit intodevfrom
fix/uninstaller-smart-cleanup

Conversation

@doITmagic
Copy link
Copy Markdown
Owner

Description

Eliminates incomplete uninstallation caused by hardcoded directory guesses (~/Projects, ~/code, etc.). Replaces with a data-driven strategy that queries every authoritative source available on the system. Also fixes the root cause of nested .ragcode/ directories being created when IDEs open sub-directories.

Changes:

  • internal/uninstall/uninstall.go — cleanup from 4 sources: registry, Qdrant file_path payloads, JetBrains/VSCode/AI-IDE configs (resolveIDEPaths), shallow $HOME scan
  • internal/uninstall/uninstall_test.go — updated tests for orphan sibling detection
  • pkg/workspace/detector/detector.go — removed .ragcode from workspace markers (prevented hijacking root detection)
  • pkg/workspace/resolver/resolver.go — centralized applyNestedOverride to prevent split-indexing on sub-directory opens

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Checklist:

  • I have performed a self-review of my own code
  • I have formatted my code with go fmt ./...
  • I have run tests go test ./... and they pass
  • I have verified integration with Ollama/Qdrant (if applicable)
  • I have updated the documentation accordingly

- Uninstaller derives scan roots from registry, Qdrant file_path payloads,
  JetBrains/VSCode/AI-IDE configs — no more hardcoded directory guessing
- findWorkspaceRootFromFilePath: walk up from Qdrant payload to workspace root
- detectIDEProjectParents: reads recentProjects.xml, storage.json, resolveIDEPaths
- detector: removed .ragcode from workspace markers (prevents hijacking root detection)
- resolver: applyNestedOverride centralized — prevents split-indexing on sub-dirs
Copilot AI review requested due to automatic review settings March 30, 2026 06:54
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Improves uninstall completeness by replacing hardcoded directory guesses with data-driven discovery (registry, Qdrant payloads, IDE configs, shallow home scan) and reduces accidental creation of nested .ragcode/ directories by adjusting workspace detection/resolution.

Changes:

  • Expand uninstall cleanup strategy to scan authoritative sources (registry + Qdrant + IDE configs + home shallow scan).
  • Remove .ragcode as a workspace marker and centralize nested-workspace override logic in the resolver.
  • Update uninstall tests and bump CLI version.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
internal/uninstall/uninstall.go New discovery-based scanning for orphaned .ragcode/ dirs; IDE/Qdrant-based root discovery.
internal/uninstall/uninstall_test.go Updates expectations to reflect fallback scan cleanup.
pkg/workspace/detector/detector.go Removes .ragcode from marker list to avoid workspace root “hijacking”.
pkg/workspace/resolver/resolver.go Centralizes nested workspace override via applyNestedOverride.
cmd/rag-code-mcp/main.go Version bump to 2.1.87.
Comments suppressed due to low confidence (1)

pkg/workspace/detector/detector.go:62

  • Removing ".ragcode" from the marker list means detectFromMetadata() can now fail for workspaces that only have the managed .ragcode/root metadata (and no other markers like .git/go.mod). That makes previously indexable “markerless” folders undiscoverable and will return "metadata referenced root ... but no markers found". Consider treating the metadata file itself as sufficient, or adding a dedicated marker check for .ragcode/root without reintroducing .ragcode directory hijacking.
			"artisan",
			".agent",
			".idea",
			".vscode",
			".vs",
			".cursor",
			".windsurf",
			"AGENTS.md",
			"CLAUDE.md",
		},
		MaxDepth:         10,
		MetadataFileName: filepath.Join(".ragcode", "root"),
	}

Comment on lines +634 to +676
func extractWorkspaceRootsFromQdrant() []string {
const qdrantAddr = "http://localhost:6333"

conn, err := net.DialTimeout("tcp", "127.0.0.1:6333", 2*time.Second)
if err != nil {
return nil // Qdrant not running — skip silently
}
conn.Close()

// List all collections.
cmd := exec.Command("curl", "-s", qdrantAddr+"/collections")
output, err := cmd.Output()
if err != nil {
return nil
}

var listResp struct {
Result struct {
Collections []struct {
Name string `json:"name"`
} `json:"collections"`
} `json:"result"`
}
if err := json.Unmarshal(output, &listResp); err != nil {
return nil
}

seen := make(map[string]struct{})
var roots []string

for _, col := range listResp.Result.Collections {
if !strings.HasPrefix(col.Name, "ragcode-") {
continue
}

// Scroll 1 point with payload to get a file_path sample.
scrollPayload := `{"limit":1,"with_payload":true,"with_vector":false}`
scrollCmd := exec.Command("curl", "-s", "-X", "POST",
qdrantAddr+"/collections/"+col.Name+"/points/scroll",
"-H", "Content-Type: application/json",
"-d", scrollPayload)
scrollOut, err := scrollCmd.Output()
if err != nil {
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

extractWorkspaceRootsFromQdrant(): the curl invocations have no timeout/deadline. If Qdrant is reachable but slow/hung, uninstall can block indefinitely on cmd.Output(). Consider using net/http with a client timeout, or add curl timeouts (e.g. --max-time / --connect-timeout) and/or context cancellation.

Copilot uses AI. Check for mistakes.
Comment on lines +337 to +349
func (r *Resolver) applyNestedOverride(ctx context.Context, candidate *contract.WorkspaceCandidate) {
if r.deps.Registry != nil && candidate.Source != "registry_fallback" {
if parentRoot, found := r.deps.Registry.FindParentWorkspace(candidate.Root); found {
r.log(ctx, "nested_workspace_override", map[string]any{
"original_root": candidate.Root,
"parent_root": parentRoot,
"reason": "candidate root is subdirectory of registered workspace",
})
candidate.Root = parentRoot
candidate.Source = "nested_workspace_override"
}
}
}
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

applyNestedOverride() mutates candidate.Root and sets Source="nested_workspace_override", but callers like handleWorkspaceRoot/handleRoots keep the original Reason/Confidence (e.g. 1.0 for explicit root). This can produce responses that claim ReasonExplicitWorkspaceRoot with high confidence while actually returning the parent workspace. Consider either not applying the override for explicit workspace_root/roots candidates, or when an override occurs also adjust Reason/Confidence and mark it as a fallback source (e.g. via isFallbackSource).

Copilot uses AI. Check for mistakes.
continue
}
// config file's parent dir (the IDE data dir, e.g. ~/.cursor)
addProject(filepath.Dir(ide.path))
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

detectIDEProjectParents(): addProject() already takes filepath.Dir(projectPath), so calling addProject(filepath.Dir(ide.path)) adds the grandparent of the IDE config file (and not the IDE data dir described in the comment). If you intended to add the config’s parent dir, call a helper that adds the dir verbatim (or change addProject to accept a parent dir), and if you want “both levels” add both explicitly.

Suggested change
addProject(filepath.Dir(ide.path))
dataDir := filepath.Dir(ide.path)
addProject(filepath.Join(dataDir, ".ragcode-ide-sentinel"))

Copilot uses AI. Check for mistakes.
}
rel, _ := filepath.Rel(root, path)
if strings.Count(rel, string(os.PathSeparator)) > 4 {
if strings.Count(rel, string(os.PathSeparator)) > maxDepth {
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

scanAndCleanRagcodeDirs(): the depth limit is off-by-one vs the comment. With maxDepth=1 for $HOME, strings.Count(rel, sep) > maxDepth still allows paths two levels deep (e.g. "a/b" has count=1). If the intent is truly “depth 1 only”, adjust the comparison (or compute depth as separators+1) so $HOME scans don’t go deeper than intended.

Suggested change
if strings.Count(rel, string(os.PathSeparator)) > maxDepth {
depth := 0
if rel != "." {
depth = strings.Count(rel, string(os.PathSeparator)) + 1
}
if depth > maxDepth {

Copilot uses AI. Check for mistakes.
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