Skip to content

feat: add per-workspace indexing opt-in and stop/cancel control#11456

Open
JamesRobert20 wants to merge 9 commits intoRooCodeInc:mainfrom
JamesRobert20:feat/indexing-workspace-opt-in-and-stop-control
Open

feat: add per-workspace indexing opt-in and stop/cancel control#11456
JamesRobert20 wants to merge 9 commits intoRooCodeInc:mainfrom
JamesRobert20:feat/indexing-workspace-opt-in-and-stop-control

Conversation

@JamesRobert20
Copy link

@JamesRobert20 JamesRobert20 commented Feb 13, 2026

Related GitHub Issue

Closes: #11455
Related: #10569

Description

Adds two features and one bug fix to the codebase indexing system:

  1. Per-workspace opt-in: Indexing no longer auto-starts on every workspace. A new codeIndexWorkspaceEnabled:<folderPath> flag (stored in workspaceState, keyed per folder) requires users to explicitly enable indexing per root folder. In multi-root workspaces, enabling one folder does not enable others. The choice is remembered across sessions.

  2. Stop/cancel indexing: Users can stop an in-progress indexing operation via a "Stop Indexing" button. Uses AbortController/AbortSignal threaded through the orchestrator → scanner pipeline with graceful abort at file and batch boundaries. Debounced cache writes are flushed on abort to preserve progress.

  3. Disable toggle bug fix: Unchecking "Enable Codebase Indexing" during active indexing now properly stops the scan via stopIndexing() instead of only calling stopWatcher(), which left the scanner running asynchronously and caused the yellow pulsing indicator to persist.

  4. Configurable auto-enable default: A global "Auto-enable indexing for new workspaces" toggle (default: ON) preserves backward compatibility while allowing users to opt into per-workspace control by setting it to OFF.

Test Procedure

Automated tests (35 tests, all passing):

  • Scanner abort signal propagation (3 new tests)
  • Orchestrator stop/cancel flow (3 new tests)
  • Manager workspace-enabled gating (3 new tests)
  • Manager stopIndexing delegation and handleSettingsChange fix (3 new tests)
  • Per-folder isolation: enabling folder A does not enable folder B (1 new test)

Manual testing:

  • Open workspace → verify indexing does NOT auto-start
  • Enable workspace indexing via toggle → verify it starts
  • Click "Stop Indexing" mid-scan → verify graceful stop with "Stopping..." state
  • Close/reopen workspace → verify remembered preference
  • Disable global toggle during active indexing → verify scan actually stops
  • Multi-root workspace: enable indexing for one folder, verify others remain disabled

Pre-Submission Checklist

  • Issue Linked ([ENHANCEMENT] feat: Per-workspace indexing opt-in and stop/cancel indexing controls #11455)
  • Scope: Focused on indexing opt-in and stop control
  • Self-Review completed
  • Testing: New tests added for all changed components (35 tests pass)
  • Lint: pnpm lint passes
  • Type Check: pnpm check-types passes
  • Knip: pnpm knip — no new unused exports
  • Translations: All 18 locales have new i18n keys (actually translated, not English placeholders)
  • Prettier: All files formatted correctly
  • Changeset: minor bump created

Documentation Updates

  • Yes: New workspace toggle and stop button need docs update

Screenshots / Videos

img1 img2 img3 img4

James Mtendamema added 2 commits February 12, 2026 22:02
- Add codeIndexWorkspaceEnabled flag in workspaceState (default: false)
- Thread AbortController/AbortSignal through orchestrator → scanner
- Add Stop Indexing button and Stopping state to UI
- Fix handleSettingsChange() to abort active scan when disabling toggle
- Add translations for all 18 locales
- Re-throw AbortError in scanner's file processing catch block to prevent
  abort signals from being silently swallowed as file errors
- Reorder stopWatcher() before setSystemState() in orchestrator abort
  catch path to ensure watcher cleanup before state transition
- Update scanner test to assert AbortError propagation on mid-scan abort
@roomote
Copy link
Contributor

roomote bot commented Feb 13, 2026

Rooviewer Clock   See task

All previously flagged issues have been addressed as of 332bd0e. The multi-root setAutoEnableDefault handler now correctly iterates all CodeIndexManager instances, capturing prior states before the global change and applying stop/start to each affected manager. No new issues found.

  • Move workspace-enabled check before _recreateServices() in initialize() to avoid creating Qdrant connections, embedder instances, and other services for every disabled workspace on extension startup
  • Translate new i18n keys (indexingStopped, indexingStoppedPartial, stopping, stopIndexingButton, stoppingButton, workspaceToggleLabel, workspaceDisabledMessage) in all 17 non-English locale files
  • setAutoEnableDefault handler should stop/start indexing when the default change flips the effective isWorkspaceEnabled state (same pattern as toggleWorkspaceIndexing)
  • setAutoEnableDefault handler only affects the current workspace manager; in multi-root workspaces, other managers that fall back to the default won't have their indexing stopped/started
Previous reviews

Mention @roomote in a comment to request specific changes to this pull request or fix all unresolved issues.

Comment on lines 162 to 166
// 5. Check workspace-level enablement
if (!this.isWorkspaceEnabled) {
this._stateManager.setSystemState("Standby", "Indexing not enabled for this workspace")
return { requiresRestart }
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This workspace-enabled check runs after _recreateServices() (step 4, a few lines above). On extension activation, initialize() is called for every workspace folder (extension.ts:182). Since isWorkspaceEnabled defaults to false, every workspace with global indexing enabled will create Qdrant connections, embedder instances, run embedder validation (potentially making network calls), instantiate scanner/fileWatcher, etc. -- then discard all of it here. Moving this check before the needsServiceRecreation / _recreateServices() block would avoid that wasted work on every disabled workspace.

Fix it with Roo Code or mention @roomote and request a fix.

Comment on lines 72 to 73
"indexingRequiresWorkspace": "Indexierung erfordert einen offenen Workspace-Ordner",
"indexingStopped": "Indexing stopped by user.",
Copy link
Contributor

Choose a reason for hiding this comment

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

The new keys (indexingStopped, indexingStoppedPartial) are in English here and across all 17 non-English locale files (embeddings.json, chat.json, settings.json). The existing keys in these files are properly translated (e.g., this file has "Datei-Watcher gestoppt." above). Leaving these untranslated means non-English users will see English strings for the new stop/workspace controls while the rest of the UI is localized.

Fix it with Roo Code or mention @roomote and request a fix.

…abort handling

- Move workspace-enabled check before _recreateServices() in initialize()
  to avoid creating Qdrant/embedder connections for disabled workspaces
- Translate new i18n keys (indexingStopped, indexingStoppedPartial, stopping,
  stopIndexingButton, stoppingButton, workspaceToggleLabel,
  workspaceDisabledMessage) in all 17 non-English locales
- Re-throw AbortError in scanner catch block to prevent silent swallowing
- Reorder stopWatcher() before setSystemState() in orchestrator abort path
- Update scanner test to assert AbortError propagation on mid-scan abort
- Fix recoverFromError test for workspace-enabled check ordering
Copy link
Contributor

@0xMink 0xMink left a comment

Choose a reason for hiding this comment

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

Blocking clarification (semantics): In this repo, indexing runtime instances are per-workspace-folder (one manager/indexer per root), but the PR stores codeIndexWorkspaceEnabled as a single workspaceState boolean. That makes enablement effectively per-window, not per-folder, in multi-root workspaces.

Issue #11455 explicitly calls out multi-root severity; please confirm the intended granularity:

  1. "Enable indexing for this workspace" == enable for the whole multi-root window (all folders), or
  2. Enable per root folder inside a multi-root workspace.

If (2) is intended, the fix is to store per-folder enablement keyed by folder URI/path (e.g., workspaceState.get("codeIndexWorkspaceEnabled:<folderKey>")), plus a unit test that proves folder A enabled does not enable folder B.

Non-blocking follow-ups worth tracking:

  • dispose() should abort any in-flight scan (avoids orphaned indexing after folder removal / deactivation).
  • Scanner back-pressure loop should check abort signal while waiting.
  • Consider flushing debounced cache writes on abort (avoids unnecessary re-index on restart).

… translate i18n

Addresses 0xMink review feedback:
- Store workspace enablement keyed by folder path to support multi-root
  workspaces (codeIndexWorkspaceEnabled:<path> instead of single boolean)
- Add test proving folder A enabled does not enable folder B
- dispose() now calls stopIndexing() to abort orphaned scans on folder removal
- Scanner back-pressure loop checks abort signal to avoid spin-waiting
- Move workspace-enabled check before _recreateServices() in initialize()
- Translate new i18n keys in all 17 non-English locales
- Fix abort handling in orchestrator and scanner catch blocks
@JamesRobert20
Copy link
Author

JamesRobert20 commented Feb 13, 2026

Good catch, workspaceState is per-window. Switched the key to codeIndexWorkspaceEnabled:<folderPath> so each root folder gets its own toggle in multi-root workspaces. Added an isolation test too.

Also addressed the other issues brought up:

  • dispose() calls stopIndexing() now instead of stopWatcher()
  • abort signal check in the scanner back-pressure while loop
  • added CacheManager.flush() which is called on abort so debounced hash writes actually get persisted

Comment on lines 2701 to 2705
await manager.setAutoEnableDefault(message.bool ?? true)
provider.postMessageToWebview({
type: "indexingStatusUpdate",
values: manager.getCurrentStatus(),
})
Copy link
Contributor

Choose a reason for hiding this comment

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

When autoEnableDefault flips from true to false, the handler persists the setting and sends a status update, but it does not stop the orchestrator on workspaces that were implicitly enabled (no explicit per-workspace toggle). Since isWorkspaceEnabled falls back to autoEnableDefault, the workspace toggle in the UI will flip to unchecked, yet the indexer keeps running in the background. The toggleWorkspaceIndexing handler at line 2680 handles the analogous case by calling manager.stopIndexing(). This handler should do the same when the effective isWorkspaceEnabled becomes false.

Suggested change
await manager.setAutoEnableDefault(message.bool ?? true)
provider.postMessageToWebview({
type: "indexingStatusUpdate",
values: manager.getCurrentStatus(),
})
const wasEnabled = manager.isWorkspaceEnabled
await manager.setAutoEnableDefault(message.bool ?? true)
const isNowEnabled = manager.isWorkspaceEnabled
if (wasEnabled && !isNowEnabled) {
manager.stopIndexing()
} else if (!wasEnabled && isNowEnabled && manager.isFeatureEnabled && manager.isFeatureConfigured) {
await manager.initialize(provider.contextProxy)
manager.startIndexing()
}
provider.postMessageToWebview({
type: "indexingStatusUpdate",
values: manager.getCurrentStatus(),
})

Fix it with Roo Code or mention @roomote and request a fix.

@0xMink
Copy link
Contributor

0xMink commented Feb 13, 2026

I compared against the latest PR head (d36d029). Three small deltas remain:

  1. URI-keyed enablement: Key per-folder enablement by workspaceFolder.uri.toString(true) instead of raw fsPath. Prevents scheme/authority collisions — e.g. vscode-remote://ssh-remote+host/project vs file:///project would currently share the same key.

  2. Abort propagation in back-pressure loop: The if (signal?.aborted) break inside while (pendingBatchCount >= MAX_PENDING_BATCHES) only exits the inner loop. The scan continues accumulating the next batch. Should be throw new DOMException("Indexing aborted", "AbortError") to propagate up correctly (matching the existing pattern 3 lines above).

  3. stopWatcher() on early-return abort paths: The two if (signal.aborted) { ... return } blocks (incremental scan line 187, full scan line 247) flush the cache but don't stop the file watcher. The catch-block abort path already calls stopWatcher() — these two should match.

Patch (applies cleanly on d36d029): https://gist.github.com/0xMink/51a7326f4744fc0b93d760b103da9dd4

All 35 tests pass (manager 20, orchestrator 5, scanner 10). The patch also replaces the existing per-folder test with a stronger multi-root isolation test using two managers sharing the same workspaceState.

@0xMink
Copy link
Contributor

0xMink commented Feb 13, 2026

Follow-up: rechecked against current PR head (051f80e). The three deltas remain:

  1. URI-keyed enablementworkspaceState key should use the resolved folder URI (folderUri.toString(true)) instead of raw fsPath, to avoid collisions across remote/multi-root workspaces
  2. Abort propagation — back-pressure loop in scanner uses break which only exits the inner while; should throw new DOMException("Indexing aborted", "AbortError") to propagate correctly
  3. Watcher cleanup — both early-return abort paths in orchestrator (lines 187-191, 247-251) are missing this.stopWatcher(), leaking the file watcher

The latest commit (051f80e) only touches webviewMessageHandler.ts, so the existing patch gist applies cleanly without modification: https://gist.github.com/0xMink/51a7326f4744fc0b93d760b103da9dd4

@JamesRobert20
Copy link
Author

JamesRobert20 commented Feb 13, 2026

Follow-up: rechecked against current PR head (051f80e). The three deltas remain:

  1. URI-keyed enablementworkspaceState key should use the resolved folder URI (folderUri.toString(true)) instead of raw fsPath, to avoid collisions across remote/multi-root workspaces
  2. Abort propagation — back-pressure loop in scanner uses break which only exits the inner while; should throw new DOMException("Indexing aborted", "AbortError") to propagate correctly
  3. Watcher cleanup — both early-return abort paths in orchestrator (lines 187-191, 247-251) are missing this.stopWatcher(), leaking the file watcher

The latest commit (051f80e) only touches webviewMessageHandler.ts, so the existing patch gist applies cleanly without modification: https://gist.github.com/0xMink/51a7326f4744fc0b93d760b103da9dd4

Should now be addressed in latest commit

@JamesRobert20 JamesRobert20 force-pushed the feat/indexing-workspace-opt-in-and-stop-control branch from 765af53 to d67e0e2 Compare February 13, 2026 08:52
@JamesRobert20 JamesRobert20 force-pushed the feat/indexing-workspace-opt-in-and-stop-control branch from d67e0e2 to 6f4be50 Compare February 13, 2026 09:03
@JamesRobert20 JamesRobert20 marked this pull request as ready for review February 13, 2026 17:35
@dosubot dosubot bot added size:XL This PR changes 500-999 lines, ignoring generated files. bug Something isn't working Enhancement New feature or request labels Feb 13, 2026
@roomote
Copy link
Contributor

roomote bot commented Feb 13, 2026

Rooviewer Clock   See task

All three previously flagged issues are resolved as of 6f4be50. One new low-severity issue found in the setAutoEnableDefault handler for multi-root workspaces.

  • Move workspace-enabled check before _recreateServices() in initialize() to avoid creating Qdrant connections, embedder instances, and other services for every disabled workspace on extension startup
  • Translate new i18n keys (indexingStopped, indexingStoppedPartial, stopping, stopIndexingButton, stoppingButton, workspaceToggleLabel, workspaceDisabledMessage) in all 17 non-English locale files
  • setAutoEnableDefault handler should stop/start indexing when the default change flips the effective isWorkspaceEnabled state (same pattern as toggleWorkspaceIndexing)
  • setAutoEnableDefault handler only affects the current workspace manager; in multi-root workspaces, other managers that fall back to the default won't have their indexing stopped/started

Mention @roomote in a comment to request specific changes to this pull request or fix all unresolved issues.

Comment on lines 2694 to 2719
case "setAutoEnableDefault": {
try {
const manager = provider.getCurrentWorkspaceCodeIndexManager()
if (!manager) {
provider.log("Cannot set auto-enable default: No workspace folder open")
return
}
const wasEnabled = manager.isWorkspaceEnabled
await manager.setAutoEnableDefault(message.bool ?? true)
const isNowEnabled = manager.isWorkspaceEnabled
if (wasEnabled && !isNowEnabled) {
manager.stopIndexing()
} else if (!wasEnabled && isNowEnabled && manager.isFeatureEnabled && manager.isFeatureConfigured) {
await manager.initialize(provider.contextProxy)
manager.startIndexing()
}
provider.postMessageToWebview({
type: "indexingStatusUpdate",
values: manager.getCurrentStatus(),
})
} catch (error) {
provider.log(
`Error setting auto-enable default: ${error instanceof Error ? error.message : String(error)}`,
)
}
break
Copy link
Contributor

Choose a reason for hiding this comment

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

setAutoEnableDefault() writes to globalState, which affects isWorkspaceEnabled on every CodeIndexManager instance that has no explicit per-workspace toggle. However, this handler only calls stopIndexing() / startIndexing() on the current workspace's manager. In a multi-root workspace where folders B and C have no explicit workspace-level setting, flipping autoEnableDefault from true to false would leave their orchestrators indexing in the background while the UI (on next status request) would show them as disabled. The inverse (false to true) would similarly fail to start indexing on the other folders. A fix could iterate over all CodeIndexManager instances (e.g., via a new static accessor) and apply the stop/start logic to each affected manager.

Fix it with Roo Code or mention @roomote and request a fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working Enhancement New feature or request size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ENHANCEMENT] feat: Per-workspace indexing opt-in and stop/cancel indexing controls

2 participants