From d12decc45c5558d3104ddcf6d379d4bcec4199e4 Mon Sep 17 00:00:00 2001 From: Tanner Bennett <8371943+NSExceptional@users.noreply.github.com> Date: Thu, 30 Jul 2026 17:27:18 -0500 Subject: [PATCH] fix(RovoDev): store binary bundle in global storage, not per-workspace The Rovo Dev CLI bundle (Python runtime, tree-sitter language pack, ripgrep, native libs; ~400MB) is version-pinned and byte-identical across workspaces, but GetRovoDevURIs rooted it at context.storageUri, so a full copy was downloaded into every workspace's storage folder. On a machine with 34 workspaces this consumed ~14GB. Root it at context.globalStorageUri instead. The existing per-version subdirectory already provides a safe dedup/cache key, so the bundle is now downloaded once per version and shared across all workspaces. This also removes the storageUri non-null assertion, which could be undefined when no workspace is open. Additionally, remove any leftover per-workspace bundle from the old location when a workspace initializes Rovo Dev, so existing installs reclaim the duplicated storage automatically rather than requiring manual cleanup. --- CHANGELOG.md | 4 +++ src/rovo-dev/rovoDevProcessManager.ts | 42 +++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1c944d83..c36d156b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - Fixed arbitrary code execution via `core.fsmonitor` in repository `.git/config` +### Bug Fixes + +- **RovoDev**: Store the downloaded Rovo Dev binary bundle in global storage instead of per-workspace storage. The bundle is version-pinned and identical across workspaces, so it was previously duplicated (~400MB each) into every workspace's storage folder. Leftover per-workspace copies are now removed automatically when a workspace is opened. + ## What's new in 4.0.31 ### Bug Fixes diff --git a/src/rovo-dev/rovoDevProcessManager.ts b/src/rovo-dev/rovoDevProcessManager.ts index 9a02cee19..4909b27c8 100644 --- a/src/rovo-dev/rovoDevProcessManager.ts +++ b/src/rovo-dev/rovoDevProcessManager.ts @@ -36,8 +36,11 @@ const RovoDevInfo = { export function GetRovoDevURIs(context: ExtensionContext) { const platform = process.platform; const arch = process.arch; - const extensionPath = context.storageUri!.fsPath; - const rovoDevBaseDir = path.join(extensionPath, 'atlascode-rovodev-bin'); + // The binary bundle is version-pinned and identical across workspaces, so store it in + // global storage (shared, keyed by version) rather than per-workspace storage. Using + // storageUri duplicated the ~400MB bundle into every workspace's storage folder. + const globalStoragePath = context.globalStorageUri.fsPath; + const rovoDevBaseDir = path.join(globalStoragePath, 'atlascode-rovodev-bin'); const rovoDevVersionDir = path.join(rovoDevBaseDir, MIN_SUPPORTED_ROVODEV_VERSION); const rovoDevBinPath = path.join(rovoDevVersionDir, 'atlassian_cli_rovodev') + (platform === 'win32' ? '.exe' : ''); @@ -219,6 +222,39 @@ export abstract class RovoDevProcessManager { this.rovoDevInstance = undefined; } + /** Ensures the legacy per-workspace binary cleanup only runs once per session. */ + private static legacyBinaryCleanupDone = false; + + /** + * Prior versions stored the Rovo Dev binary bundle under per-workspace storage + * (`context.storageUri`), which duplicated the ~400MB bundle into every workspace's + * storage folder. The bundle now lives in global storage, so remove any leftover + * per-workspace copy for the current workspace to reclaim the space automatically. + */ + private static async cleanupLegacyWorkspaceBinary(context: ExtensionContext) { + if (this.legacyBinaryCleanupDone) { + return; + } + this.legacyBinaryCleanupDone = true; + + // storageUri is undefined when no workspace/folder is open; in that case there is + // no legacy per-workspace bundle to clean up. + const workspaceStoragePath = context.storageUri?.fsPath; + if (!workspaceStoragePath) { + return; + } + + const legacyBinDir = path.join(workspaceStoragePath, 'atlascode-rovodev-bin'); + try { + if (fs.existsSync(legacyBinDir)) { + await getFsPromise((callback) => fs.rm(legacyBinDir, { recursive: true, force: true }, callback)); + Logger.info(`Removed legacy per-workspace Rovo Dev binary bundle at ${legacyBinDir}`); + } + } catch (error) { + Logger.warn(`Failed to remove legacy per-workspace Rovo Dev binary bundle: ${error}`); + } + } + public static get state(): RovoDevProcessState { if (RovoDevProcessManager.extensionApi.metadata.isBoysenberry()) { const httpPort = parseInt(process.env[RovoDevInfo.envVars.port] || '0'); @@ -364,6 +400,8 @@ export abstract class RovoDevProcessManager { } public static async initializeRovoDev(context: ExtensionContext, forceNewInstance?: boolean) { + await this.cleanupLegacyWorkspaceBinary(context); + if (this.asyncLocked) { throw new Error('Multiple initialization of Rovo Dev attempted'); }