Skip to content

[bug/3] Added error handling#10

Merged
justinkumpe merged 1 commit intomainfrom
bug/3
Feb 13, 2026
Merged

[bug/3] Added error handling#10
justinkumpe merged 1 commit intomainfrom
bug/3

Conversation

@justinkumpe
Copy link
Member

@justinkumpe justinkumpe commented Feb 13, 2026

Summary by Sourcery

Add robust error handling and user-facing error messages across Git command handlers in the VS Code extension.

Bug Fixes:

  • Prevent extension from failing silently or throwing unhandled exceptions when Git operations such as status, add, commit, push, pull, and history rewrites encounter errors.

Enhancements:

  • Provide clearer, operation-specific error notifications for failures in Git workflows including branching, rebasing, cleanup, stash, tagging, remotes, and utility commands.
  • Standardize success and failure messaging for multi-step flows like add/commit/push and history rewrite actions.

Tests:

  • Remove an obsolete test artifact file.

Chores:

  • Remove legacy .GIT_QUICKOPS_PREFIX configuration file in favor of the consolidated .GIT_QUICKOPS_CONFIG.

@justinkumpe justinkumpe self-assigned this Feb 13, 2026
@justinkumpe justinkumpe linked an issue Feb 13, 2026 that may be closed by this pull request
@sourcery-ai
Copy link

sourcery-ai bot commented Feb 13, 2026

Reviewer's Guide

Adds consistent try/catch error handling and user-facing error messages around most Git command handlers in the VS Code extension, and removes some legacy config/test artifacts.

Sequence diagram for generic Git command error handling

sequenceDiagram
    actor VSCodeUser
    participant VSCodeExtension
    participant RepositoryContext
    participant git
    participant vscode_window

    VSCodeUser->>VSCodeExtension: run cmdStatus
    VSCodeExtension->>VSCodeExtension: try
    VSCodeExtension->>RepositoryContext: getGitRoot()
    RepositoryContext-->>VSCodeExtension: gitRoot
    VSCodeExtension->>git: runGitCommand(gitRoot, [status], Git_Status)
    git-->>VSCodeExtension: result
    VSCodeExtension-->>VSCodeUser: command completes

    alt command throws error
        VSCodeExtension->>vscode_window: showErrorMessage(Failed to show status: error)
    end
Loading

Sequence diagram for Add-Commit-Push command with error handling

sequenceDiagram
    actor VSCodeUser
    participant VSCodeExtension
    participant RepositoryContext
    participant git
    participant resolvePushTarget
    participant vscode_window

    VSCodeUser->>VSCodeExtension: run cmdAddCommitPush
    VSCodeExtension->>VSCodeExtension: try
    VSCodeExtension->>RepositoryContext: getGitRoot()
    RepositoryContext-->>VSCodeExtension: gitRoot

    VSCodeExtension->>vscode_window: showInformationMessage(Running tests...)
    VSCodeExtension->>git: checkTestsBeforeCommit(gitRoot)
    git-->>VSCodeExtension: testResult

    alt noTests
        VSCodeExtension->>vscode_window: showInformationMessage(No tests found - proceeding with commit)
    else testsPassed
        VSCodeExtension->>vscode_window: showInformationMessage(Tests passed successfully)
    end

    alt !testResult.canProceed
        VSCodeExtension->>vscode_window: showErrorMessage(testResult.message or Commit blocked by tests)
        VSCodeExtension-->>VSCodeUser: return
    else testResult.message contains Warning
        VSCodeExtension->>vscode_window: showWarningMessage(testResult.message, Commit_Anyway, Cancel)
        vscode_window-->>VSCodeExtension: choice
        alt choice != Commit_Anyway
            VSCodeExtension-->>VSCodeUser: return
        end
    end

    VSCodeExtension->>git: execGit(gitRoot, [add, -A])
    git-->>VSCodeExtension: ok
    VSCodeExtension->>vscode_window: showInformationMessage(Changes added)

    VSCodeExtension->>git: getProcessedPrefix(gitRoot)
    git-->>VSCodeExtension: prefix
    VSCodeExtension->>vscode_window: showInputBox(Enter commit message)
    vscode_window-->>VSCodeExtension: message
    alt no message
        VSCodeExtension-->>VSCodeUser: return
    else message provided
        VSCodeExtension->>git: execGit(gitRoot, [commit, -m, message])
        git-->>VSCodeExtension: ok
        VSCodeExtension->>vscode_window: showInformationMessage(Changes committed)

        VSCodeExtension->>git: getCurrentBranch(gitRoot)
        git-->>VSCodeExtension: branch
        VSCodeExtension->>resolvePushTarget: resolvePushTarget(gitRoot, branch)
        resolvePushTarget-->>VSCodeExtension: pushTarget or null
        alt no pushTarget
            VSCodeExtension-->>VSCodeUser: return
        else pushTarget
            VSCodeExtension->>vscode_window: withProgress(Pushing to pushTarget.display)
            VSCodeExtension->>git: execGit(gitRoot, [push, pushTarget.remote, branch])
            git-->>VSCodeExtension: ok
            VSCodeExtension->>vscode_window: showInformationMessage(Changes pushed to pushTarget.display)
        end
    end

    opt any error in try block
        VSCodeExtension->>vscode_window: showErrorMessage(Failed to add/commit/push: error)
    end
Loading

File-Level Changes

Change Details Files
Wrap Git command entrypoints in try/catch blocks and surface failures via vscode error notifications instead of letting exceptions bubble.
  • Updated cmdStatus, cmdLog, cmdDiff, cmdPull, cmdFetch, cmdPush, cmdMerge, cmdHistoryRebaseOnto, cmdHistorySquashN, cmdHistoryUndoLast, cmdHistoryAmendMessage, cmdCleanupPruneFetch, cmdStashSave, cmdStashList, cmdStashPop, cmdTagCreate, cmdRemotesSetUpstream, cmdUtilsRestoreFile, cmdUtilsUnstageAll, cmdUtilsSetPrefix to be wrapped in try/catch
  • On failures, show contextual vscode.window.showErrorMessage messages like 'Failed to push', 'Failed to show diff', etc.
  • Left the core Git operations and prompts unchanged inside the try blocks, only augmenting with error reporting
src/extension.ts
Harden multi-step flows (add/commit/push, history rewrite, cleanup, branch management) with top-level error handling while preserving existing guard rails and user confirmations.
  • Wrapped cmdAdd, cmdCommit, cmdAddCommitPush, cmdHistoryRewriteToSingle, cmdCleanupDeleteOrphans, cmdCleanupDeleteMerged, cmdBranchCreate, cmdBranchCreateFrom, cmdBranchSwitch, cmdBranchRename, cmdBranchDelete in try/catch blocks
  • Kept existing validation flows (status checks, test checks, confirmations, quick picks) inside try blocks so early returns still behave as before
  • Standardized catch-block messages to describe the high-level operation that failed (e.g., 'Failed to delete branches', 'Failed to rename branch', 'Failed to rewrite history')
src/extension.ts
Adjust some user-facing error message wording to be more operation-scoped and less implementation-specific.
  • Changed push-related catch blocks to messages like 'Failed to push' or 'Failed to add/commit/push' instead of 'Push failed'
  • Changed rebase/squash/history error messages to describe the overall operation outcome (e.g. 'Failed to rewrite history', 'Failed to start squash')
src/extension.ts
Remove legacy/unused Git QuickOps config and test artifacts from the repo.
  • Deleted legacy .GIT_QUICKOPS_PREFIX file (now superseded by .GIT_QUICKOPS_CONFIG-based config)
  • Removed a top-level test artifact/directory that is no longer needed
  • Touched .GIT_QUICKOPS_CONFIG only in the diff header (no functional change in the snippet)
.GIT_QUICKOPS_PREFIX
test
.GIT_QUICKOPS_CONFIG

Assessment against linked issues

Issue Objective Addressed Explanation
#2 Modify the cmdPull implementation so that pull operations no longer hang, by adding robust error handling around the git pull operation.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@justinkumpe justinkumpe linked an issue Feb 13, 2026 that may be closed by this pull request
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • There is a lot of repeated try/catch and test-before-commit logic across commands (status/add/commit/addCommitPush/history rewrite, etc.); consider extracting small helpers (e.g. a withGitErrorHandling(label, fn) wrapper and a runTestsOrAbort(gitRoot) utility) to reduce duplication and keep behavior consistent.
  • Some error messages have been generalized (e.g. merge now reports Failed to merge instead of Merge conflict or error and squash uses Failed to start squash instead of Failed to start rebase); double-check whether you want to preserve the more specific context to help users understand what kind of failure occurred.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- There is a lot of repeated try/catch and test-before-commit logic across commands (status/add/commit/addCommitPush/history rewrite, etc.); consider extracting small helpers (e.g. a `withGitErrorHandling(label, fn)` wrapper and a `runTestsOrAbort(gitRoot)` utility) to reduce duplication and keep behavior consistent.
- Some error messages have been generalized (e.g. merge now reports `Failed to merge` instead of `Merge conflict or error` and squash uses `Failed to start squash` instead of `Failed to start rebase`); double-check whether you want to preserve the more specific context to help users understand what kind of failure occurred.

## Individual Comments

### Comment 1
<location> `src/extension.ts:597-598` </location>
<code_context>
+    try {
+        const gitRoot = await RepositoryContext.getGitRoot();
+        await git.runGitCommand(gitRoot, ['status'], 'Git Status');
+    } catch (error) {
+        vscode.window.showErrorMessage(`Failed to show status: ${error}`);
+    }
 }
</code_context>

<issue_to_address>
**suggestion:** Interpolating raw `error` risks showing `[object Object]`; consider normalizing error messages.

This pattern appears in several commands (e.g. ``Failed to show status: ${error}``). When `error` isn’t a string, users may see `[object Object]` instead of a useful message. A small helper like `formatError(error)` that returns `error.message` for `Error` instances and `String(error)` otherwise, reused wherever `showErrorMessage` is called, would keep messages consistent and readable.

Suggested implementation:

```typescript
=======
function formatError(error: unknown): string {
    if (error instanceof Error && typeof error.message === 'string' && error.message.length > 0) {
        return error.message;
    }

    try {
        return String(error);
    } catch {
        return 'Unknown error';
    }
}

// ========== Command Implementations ==========

```

```typescript
async function cmdStatus() {
    try {
        const gitRoot = await RepositoryContext.getGitRoot();
        await git.runGitCommand(gitRoot, ['status'], 'Git Status');
    } catch (error) {
        vscode.window.showErrorMessage(`Failed to show status: ${formatError(error)}`);
    }
}

```

You mentioned this error handling pattern appears in several commands. For consistency, all similar `catch (error)` blocks that currently do something like ``vscode.window.showErrorMessage(\`...: ${error}\`)`` should be updated to call `formatError(error)` instead. The exact search/replace will depend on the rest of `src/extension.ts`, but the pattern should mirror the `cmdStatus` change above.
</issue_to_address>

### Comment 2
<location> `src/extension.ts:593` </location>
<code_context>
 // ========== Command Implementations ==========

 async function cmdStatus() {
-    const gitRoot = await RepositoryContext.getGitRoot();
-    await git.runGitCommand(gitRoot, ['status'], 'Git Status');
</code_context>

<issue_to_address>
**issue (complexity):** Consider introducing shared helpers for error-handling and test-check logic so individual cmd* functions stay flatter and less repetitive.

You can keep all the new safety/error handling while significantly reducing repetition by extracting two small helpers.

### 1. Centralize per-command error handling

Most commands now follow this pattern:

```ts
async function cmdStatus() {
    try {
        const gitRoot = await RepositoryContext.getGitRoot();
        await git.runGitCommand(gitRoot, ['status'], 'Git Status');
    } catch (error) {
        vscode.window.showErrorMessage(`Failed to show status: ${error}`);
    }
}
```

Instead of repeating `try/catch` in every command, you can centralize it:

```ts
async function runCommand<T>(
    action: string,
    fn: () => Promise<T>
): Promise<T | undefined> {
    try {
        return await fn();
    } catch (error) {
        vscode.window.showErrorMessage(`Failed to ${action}: ${error}`);
        return undefined;
    }
}
```

Then commands become flatter and easier to scan:

```ts
async function cmdStatus() {
    return runCommand('show status', async () => {
        const gitRoot = await RepositoryContext.getGitRoot();
        await git.runGitCommand(gitRoot, ['status'], 'Git Status');
    });
}

async function cmdAdd() {
    return runCommand('add changes', async () => {
        const gitRoot = await RepositoryContext.getGitRoot();
        const status = await git.getStatus(gitRoot);
        if (!status) {
            vscode.window.showInformationMessage('No changes to add');
            return;
        }
        // ... rest of current cmdAdd body unchanged ...
    });
}
```

You can apply this pattern to most of the `cmd*` functions that currently wrap their entire body in a `try/catch`.

### 2. Extract the “tests before commit” flow

The test-check logic is now duplicated in several places (`cmdCommit`, `cmdAddCommitPush`, `cmdHistoryRewriteToSingle`, `cmdCommitStaged`):

```ts
// Check tests before committing
let testResult: any;

// Show running notification (without buttons so it auto-dismisses after ~5 seconds)
vscode.window.showInformationMessage('⏳ Running tests...');

testResult = await git.checkTestsBeforeCommit(gitRoot);

// Show result message (also auto-dismisses)
if (testResult.noTests) {
    vscode.window.showInformationMessage('✅ No tests found - proceeding with commit');
} else if (testResult.passed) {
    vscode.window.showInformationMessage('✅ Tests passed successfully');
}

if (!testResult.canProceed) {
    vscode.window.showErrorMessage(testResult.message || 'Commit blocked by tests');
    return;
}
if (testResult.message && testResult.message.includes('Warning')) {
    const choice = await vscode.window.showWarningMessage(
        testResult.message,
        'Commit Anyway', 'Cancel'
    );
    if (choice !== 'Commit Anyway') {
        return;
    }
}
```

You can move this into a helper that returns a boolean:

```ts
async function ensureTestsAllowCommit(gitRoot: string): Promise<boolean> {
    vscode.window.showInformationMessage('⏳ Running tests...');

    const testResult: any = await git.checkTestsBeforeCommit(gitRoot);

    if (testResult.noTests) {
        vscode.window.showInformationMessage('✅ No tests found - proceeding with commit');
    } else if (testResult.passed) {
        vscode.window.showInformationMessage('✅ Tests passed successfully');
    }

    if (!testResult.canProceed) {
        vscode.window.showErrorMessage(testResult.message || 'Commit blocked by tests');
        return false;
    }

    if (testResult.message && testResult.message.includes('Warning')) {
        const choice = await vscode.window.showWarningMessage(
            testResult.message,
            'Commit Anyway', 'Cancel'
        );
        if (choice !== 'Commit Anyway') {
            return false;
        }
    }

    return true;
}
```

Then your commands read much more clearly:

```ts
async function cmdCommit() {
    return runCommand('commit', async () => {
        const gitRoot = await RepositoryContext.getGitRoot();

        const status = await git.execGit(gitRoot, ['status', '--porcelain']);
        const hasStagedChanges = status.split('\n').some(line => line.match(/^[MADRC]/));
        if (!hasStagedChanges) {
            vscode.window.showWarningMessage(
                'No staged changes to commit. Use "Add → Commit → Push" to stage all changes first.'
            );
            return;
        }

        if (!(await ensureTestsAllowCommit(gitRoot))) {
            return;
        }

        const prefix = await git.getProcessedPrefix(gitRoot);
        const message = await vscode.window.showInputBox({
            prompt: 'Enter commit message',
            placeHolder: 'Commit message',
            value: prefix
        });
        if (!message) {
            return;
        }

        await git.runGitCommand(gitRoot, ['commit', '-m', message], 'Git Commit');
        vscode.window.showInformationMessage('Changes committed');
    });
}
```

And similarly in `cmdAddCommitPush`, `cmdHistoryRewriteToSingle`, `cmdCommitStaged`:

```ts
if (!(await ensureTestsAllowCommit(gitRoot))) {
    return;
}
// continue with existing flow...
```

These two helpers keep all your new behavior (per-command messages and test gating) but remove a lot of boilerplate and nested `try/catch` blocks, making the file smaller and more maintainable.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@justinkumpe justinkumpe added this pull request to the merge queue Feb 13, 2026
Merged via the queue into main with commit 06482a3 Feb 13, 2026
5 checks passed
@justinkumpe justinkumpe deleted the bug/3 branch February 13, 2026 21:16
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.

[bug] Errors cause script to hang instead of reporting error [bug] Pull function hanging

1 participant