Skip to content

Startup can take minutes because activation runs duplicate full discovery #17

@MelbourneDeveloper

Description

@MelbourneDeveloper

Summary

CommandTree startup is very slow in a real VS Code workspace. The user-visible startup can take multiple minutes before the views feel usable. In the public CodeDedup workspace, the persisted CommandTree debug log shows extension activation taking about 66 seconds, with two full discovery passes started almost simultaneously.

The duplicate discovery appears to be caused by initialDiscovery() calling syncQuickTasks() -> treeProvider.refresh() while the TreeView asks getChildren() before discoveryResult is set. getChildren() then starts another full refresh(). Every discovery category in the log appears twice.

Environment

  • Extension: nimblesite.commandtree@0.10.0
  • VS Code: 1.116.0
  • OS: macOS 26.3.1 arm64
  • Workspace used for the public evidence: CodeDedup
  • .commandtree/commandtree.sqlite3: about 192 KiB
  • Discovered commands in slow startup run: 124

Observed behaviour

On extension activation, CommandTree blocks startup on full discovery and command registration. The debug log shows:

03:50:50.168 Extension activating
03:50:50.182 SQLite database initialised
03:50:50.183 CommandTreeProvider.refresh() starting
03:50:50.186 Discovery started
03:50:50.188 getChildren: no discovery result yet, triggering refresh
03:50:50.188 CommandTreeProvider.refresh() starting
03:50:50.188 Discovery started
03:50:55.431 Discovery [vscode] count=4
03:50:55.431 Discovery [vscode] count=4
03:51:52.898 Discovery [npm] count=16
03:51:53.036 Discovery [markdown] count=41
03:51:53.129 Discovery [markdown] count=41
03:51:53.134 Discovery [npm] count=16
03:51:53.517 Discovery [shell] count=1
03:51:54.124 Discovery [shell] count=1
03:51:54.353 Discovery [make] count=21
03:51:54.357 Discovery [make] count=21
03:51:54.388 Discovery [cargo] count=41
03:51:54.388 Discovery complete totalCount=124
03:51:54.393 Discovery [cargo] count=41
03:51:54.393 Discovery complete totalCount=124
03:51:56.153 Commands registered in DB count=124
03:51:56.153 [SUMMARY] Starting taskCount=124
03:51:56.153 Extension activation complete
03:51:59.077 [SUMMARY] All summaries up to date

Timing from the same log:

Event Elapsed from activation
First refresh() starts 15 ms
Second refresh() starts from getChildren() 20 ms
vscode discovery returns 5.263 s
npm/markdown discovery returns ~62.7-63.0 s
Both full discoveries complete ~64.2 s
Commands are registered ~65.985 s
Activation complete ~65.985 s

Why this looks actionable

Installed out/extension.js has this activation flow:

registerTreeViews(context);
registerCommands(context);
setupWatchers(context, workspaceRoot);
await initialDiscovery(workspaceRoot);
initAiSummaries(workspaceRoot);
logger.info("Extension activation complete");

initialDiscovery() calls:

await syncQuickTasks();
await registerDiscoveredCommands(workspaceRoot);
await syncTagsFromJson(workspaceRoot);

syncQuickTasks() calls:

await treeProvider.refresh();
quickTasksProvider.updateTasks(treeProvider.getAllTasks());

CommandTreeProvider.getChildren() currently does:

if (!this.discoveryResult) {
  logger.info("getChildren: no discovery result yet, triggering refresh");
  await this.refresh();
}

So the TreeView can re-enter discovery while the activation-triggered discovery is still in flight. In the log, that happens 2 ms after the first Discovery started line.

discoverAllTasks() runs all discoverers in Promise.all, which is good, but without an in-flight refresh guard the whole discoverer set is duplicated. The current logs also only show completion counts, so it is hard to tell which finder is responsible for the long gap before npm/markdown finish.

Expected behaviour

  • Extension activation should return quickly and should not block VS Code for a full workspace scan plus DB registration.
  • Opening the CommandTree view during startup should not trigger a second full discovery if one is already running.
  • Discovery should run once per explicit refresh/change event, or concurrent callers should share the same in-flight promise.
  • The tree can show a loading/empty state while discovery continues in the background.

Suggested fix direction

  1. Add an in-flight refresh guard in CommandTreeProvider.

    • Store a refreshPromise while discovery is running.
    • If refresh() is called again, return/await the same promise instead of starting another discoverAllTasks().
    • Clear the promise in finally.
  2. Avoid awaiting full discovery in activate().

    • Register providers/commands and return activation promptly.
    • Kick initial discovery, DB registration, tag sync, and optional summary work into a background task.
    • Fire tree updates when discovery completes.
  3. Make getChildren() non-reentrant.

    • If discovery is in progress, either await the existing promise or return a stable loading node.
    • Do not start a new refresh from getChildren() when initial discovery is already running.
  4. Add per-discoverer duration logging.

    • Log start/end/duration for each discoverer (npm, markdown, cargo, etc.).
    • This will make the next slow-start report immediately attributable.
  5. Consider startup tests around concurrent refresh.

    • A test should call refresh() and getChildren() concurrently and assert discoverAllTasks() is invoked once.
    • Another test should assert activate() does not await slow discovery before returning, if activation is changed to background discovery.

Acceptance criteria

  • In the CodeDedup-style startup sequence, the log should show only one Discovery started before the first Discovery complete.
  • Extension activation should complete before long-running workspace discovery and command registration finish.
  • Quick Launch and All views should populate after the shared discovery result is available.
  • A slow discoverer should be visible in logs with its elapsed duration.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions