From 761903c2a93c63b3ac71059c9a92869da47a2c16 Mon Sep 17 00:00:00 2001 From: Sarthak Agarwal Date: Fri, 14 Nov 2025 20:23:58 -0800 Subject: [PATCH] saga4 misc fixes (#2018) - **misc fixes [VSC]** - **misc lefthook** # Pull Request Checklist ## Description - [ ] **Description of PR**: Clear and concise description of what this PR accomplishes - [ ] **Breaking Changes**: Document any breaking changes (if applicable) - [ ] **Related Issues**: Link to any related issues or tickets ## Testing - [ ] **Test cases Attached**: All relevant test cases have been added/updated - [ ] **Manual Testing**: Manual testing completed for the changes ## Monitoring & Debugging - [ ] **Logging in place**: Appropriate logging has been added for debugging user issues - [ ] **Sentry will be able to catch errors**: Error handling ensures Sentry can capture and report errors - [ ] **Avoid Dev based/Prisma logging**: No development-only or Prisma-specific logging in production code ## Configuration - [ ] **Env variables newly added**: Any new environment variables are documented in .env.example file or mentioned in description --- ## Additional Notes --- .../src/components/initForm.tsx | 40 +++++++++++++-- .../src/styles/initForm.module.css | 51 +++++++++++++++++++ .../src/boot/bootCodeflashServer.ts | 33 +++++++++--- js/VSC-Extension/src/services/initService.ts | 15 ++++-- lefthook.yml | 3 +- 5 files changed, 128 insertions(+), 14 deletions(-) diff --git a/js/VSC-Extension/packages/sidebar-webview/src/components/initForm.tsx b/js/VSC-Extension/packages/sidebar-webview/src/components/initForm.tsx index e68160624..bfb08ee8e 100644 --- a/js/VSC-Extension/packages/sidebar-webview/src/components/initForm.tsx +++ b/js/VSC-Extension/packages/sidebar-webview/src/components/initForm.tsx @@ -63,6 +63,13 @@ const InitForm = ({ suggestions, errorDetails, pyprojectPath }: Props) => { }); const renderedOnPurpose = !errorDetails; + // Helper to get directory name from path + const getConfigDirectory = () => { + if (!pyprojectPath) return "workspace root"; + const parts = pyprojectPath.split("/"); + return parts.length > 1 ? parts.slice(0, -1).join("/") : "."; + }; + const setActiveTab = renderedOnPurpose ? useStore((state) => state.setActiveTab) : undefined; @@ -315,6 +322,20 @@ const InitForm = ({ suggestions, errorDetails, pyprojectPath }: Props) => { className={styles.form} >

Project Configuration

+ + {/* Context info section */} +
+
+ + + Config file: {pyprojectPath || "pyproject.toml (will be created)"} + +
+
+ 💡 All paths below are relative to {getConfigDirectory()} +
+
+ {errorDetails && (

` + errorDetails, "Module Root", "Path relative to pyproject.toml where your source code is located.", true, + "e.g., src, lib, app, or . for current directory" )} {renderField( "tests_root", "Tests Root", - "Path relative to pyproject.toml where your test files are located", + "Path relative to pyproject.toml where your test files are located (common: tests, test, __tests__)", true, + "e.g., tests, test, or . for current directory" )} {renderField( @@ -374,12 +397,23 @@ style="color: var(--vscode-editorWarning-foreground);">` + errorDetails, disabled={isLoading} > {isLoading ? ( - + <> + + {" Saving..."} + ) : ( - "Save Configurations" + <> + + {" Save & Apply Configuration"} + )} + {!isLoading && ( +

+ {pyprojectPath ? "This will update" : "This will create"} your pyproject.toml and reload the extension +
+ )} ); diff --git a/js/VSC-Extension/packages/sidebar-webview/src/styles/initForm.module.css b/js/VSC-Extension/packages/sidebar-webview/src/styles/initForm.module.css index ab33a0caa..6325911b9 100644 --- a/js/VSC-Extension/packages/sidebar-webview/src/styles/initForm.module.css +++ b/js/VSC-Extension/packages/sidebar-webview/src/styles/initForm.module.css @@ -33,6 +33,57 @@ color: var(--vscode-foreground); } +.contextInfo { + background: var(--vscode-input-background); + border: 1px solid var(--vscode-input-border); + border-radius: 4px; + padding: 12px; + margin: 8px 0 16px 0; + display: flex; + flex-direction: column; + gap: 8px; +} + +.contextItem { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: var(--vscode-descriptionForeground); +} + +.contextItem code { + background: var(--vscode-textCodeBlock-background); + padding: 2px 6px; + border-radius: 3px; + font-family: var(--vscode-editor-font-family); + font-size: 11px; + color: var(--vscode-textPreformat-foreground); +} + +.helpText { + font-size: 11px; + color: var(--vscode-descriptionForeground); + font-style: italic; + margin-top: 4px; +} + +.helpText code { + background: var(--vscode-textCodeBlock-background); + padding: 2px 4px; + border-radius: 3px; + font-family: var(--vscode-editor-font-family); + font-size: 10px; +} + +.actionHelp { + font-size: 11px; + color: var(--vscode-descriptionForeground); + text-align: center; + margin-top: 4px; + font-style: italic; +} + .bannerError { padding: 1rem 0.5rem; border-radius: 5px; diff --git a/js/VSC-Extension/src/boot/bootCodeflashServer.ts b/js/VSC-Extension/src/boot/bootCodeflashServer.ts index bdba6c9d0..c91f111c1 100644 --- a/js/VSC-Extension/src/boot/bootCodeflashServer.ts +++ b/js/VSC-Extension/src/boot/bootCodeflashServer.ts @@ -177,17 +177,38 @@ export class BootCodeflashServerStep extends BaseStep { }; }; + /**When + * Safely register a VS Code command with error handling. + * If a command already exists (e.g., during extension reload), log a warning + * and return a no-op disposable to prevent crashes. + */ + private safeRegisterCommand = ( + commandId: string, + callback: (...args: any[]) => any, + ): vscode.Disposable => { + try { + return vscode.commands.registerCommand(commandId, callback); + } catch (error) { + // Command already exists - this can happen during extension reload/retry + this.logger.warn( + `Command '${commandId}' already registered. This is expected during extension reload. Error: ${error}`, + ); + // Return a no-op disposable to maintain the disposal chain + return { dispose: () => {} }; + } + }; + private provideCommands = ( sidebarProvider: SidebarProvider, commentThreadProvider: CommentThreadProvider, ) => { - const optimizeFunctionCommand = vscode.commands.registerCommand( + const optimizeFunctionCommand = this.safeRegisterCommand( "codeflash.optimizeFunction", (uri: vscode.Uri, functionName: string) => { sidebarProvider.addFunctionToQueue(functionName, uri, true); }, ); - const clearAllTasksCommand = vscode.commands.registerCommand( + const clearAllTasksCommand = this.safeRegisterCommand( "codeflash.clearAllTasks", () => { const allTasksIds = this.globalState @@ -199,7 +220,7 @@ export class BootCodeflashServerStep extends BaseStep { commentThreadProvider.scheduleRefresh(); }, ); - const optimizeAllCommand = vscode.commands.registerCommand( + const optimizeAllCommand = this.safeRegisterCommand( "codeflash.optimizeAll", async () => { this.logger.info("Optimizing all functions from command"); @@ -207,7 +228,7 @@ export class BootCodeflashServerStep extends BaseStep { }, ); - const viewOptimization = vscode.commands.registerCommand( + const viewOptimization = this.safeRegisterCommand( "codeflash.viewPatch", async ( threadOrPayload: @@ -229,7 +250,7 @@ export class BootCodeflashServerStep extends BaseStep { }, ); - const acceptInlineCommentOptimizationCmd = vscode.commands.registerCommand( + const acceptInlineCommentOptimizationCmd = this.safeRegisterCommand( "codeflash.acceptInlineRefactor", async ( thread: vscode.CommentThread & { @@ -242,7 +263,7 @@ export class BootCodeflashServerStep extends BaseStep { }, ); - const sendMessage = vscode.commands.registerCommand( + const sendMessage = this.safeRegisterCommand( "codeflash.sendMessage", (message: OutgoingWebviewMessage) => { sidebarProvider.sendMessage(message); diff --git a/js/VSC-Extension/src/services/initService.ts b/js/VSC-Extension/src/services/initService.ts index 1d63256ba..d5f756da8 100644 --- a/js/VSC-Extension/src/services/initService.ts +++ b/js/VSC-Extension/src/services/initService.ts @@ -109,13 +109,22 @@ export class InitService extends Disposable { } catch (error) { if (maxRetries > 0) { this.logger.error( - `InitService init error: ${error} (${maxRetries} retries left`, + `InitService init error: ${error} (${maxRetries} retries left)`, ); + // Dispose all resources including LSP clients and command registrations this.dispose(); this._disposables = []; - // wait for half a second before retrying - await new Promise((resolve) => setTimeout(resolve, 500)); + + // Wait longer (1 second) to ensure VS Code fully unregisters commands and disposes resources + // This prevents "command already exists" errors during retry + // The delay accounts for: + // 1. Async disposal of LSP clients + // 2. VS Code's internal command registry cleanup + // 3. Event listener teardown + this.logger.info("Waiting for cleanup to complete before retry..."); + await new Promise((resolve) => setTimeout(resolve, 1000)); + return await this.initWithRetries(maxRetries - 1); } else { this.logger.error(`InitService init failed: ${error}`); diff --git a/lefthook.yml b/lefthook.yml index 843c21f88..94ae12562 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -2,10 +2,9 @@ pre-commit: parallel: true commands: secret-scan: - runner: "node" glob: "*" exclude: "node_modules/**|venv/**|.venv/**|__pycache__/**|dist/**|build/**" - command: "./node_modules/.bin/secretlint {staged_files} --maskSecrets --config ./secretlint.config.js" + run: "./node_modules/.bin/secretlint {staged_files} --maskSecrets --secretlintrc ./secretlint.config.js" # js-lint: # runner: "node"