simple-git
semver
>=3.32.3postconditions25functions25last verified2026-06-25coverage score96%Postconditions: what we check
- push · simple-git-push-missing-try-catcherrorWhenasync function calls git.push() or this.git.push() without wrapping in try-catch. Throws GitError on network failures, SSH/HTTPS authentication failures, remote rejection (non-fast-forward), or permission denied.Throws
GitError — base error class exported from simple-git. Has .message (git stderr output), .stack. Remote push errors include: "Permission denied (publickey)", "remote rejected", "rejected (fetch first)", "could not read Username", "fatal: repository not found".Required handlingCaller MUST wrap git.push() in try-catch and handle GitError. Minimum handling: try { await git.push('origin', branch); } catch (err) { console.error('Git push failed:', err.message); throw err; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - pull · simple-git-pull-missing-try-catcherrorWhenasync function calls git.pull() without wrapping in try-catch. Throws GitError on network failures or auth failures. Throws GitResponseError on merge conflicts during pull.Throws
GitError — on network/auth failures. GitResponseError<PullResult> — on merge conflicts, has .git property with conflict details.Required handlingCaller MUST wrap git.pull() in try-catch. Minimum handling: try { await git.pull('origin', branch); } catch (err) { console.error('Git pull failed:', err.message); throw err; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - clone · simple-git-clone-missing-try-catcherrorWhenasync function calls git.clone() without wrapping in try-catch. Throws GitError on network failures, auth failures, invalid repository URL, or destination path permission errors.Throws
GitError — on network/auth/path failures. Common messages: "Repository not found", "Permission denied (publickey)", "fatal: destination path already exists", "could not read Username".Required handlingCaller MUST wrap git.clone() in try-catch. Minimum handling: try { await git.clone(repoUrl, localPath); } catch (err) { console.error('Git clone failed:', err.message); throw err; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - merge · simple-git-merge-missing-try-catcherrorWhenasync function calls git.merge() without wrapping in try-catch. Throws GitResponseError<MergeSummary> on merge conflicts — EVEN WHEN the underlying git process exits with code 0. The .git.conflicts[] array contains the conflicting file paths.Throws
GitResponseError<MergeSummary> — on merge conflicts, has .git.conflicts (string[]) and .git.failed (boolean). Note: git exits 0 but simple-git still throws. GitError — on other merge failures (invalid ref, etc.).Required handlingCaller MUST wrap git.merge() in try-catch and handle merge conflicts. Minimum handling: try { await git.merge(['sourceBranch', '--no-edit']); } catch (err) { if (err.git?.conflicts?.length > 0) { console.error('Merge conflicts:', err.git.conflicts); await git.merge(['--abort']); } throw err; }costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - fetch · simple-git-fetch-missing-try-catcherrorWhenasync function calls git.fetch() or git.fetch(remote, branch) without wrapping in try-catch. Throws GitError on network failure, SSH/HTTPS authentication failure, remote not found, or git binary not installed. Common in background sync workers that run fetch periodically.Throws
GitError — on any failure: network error (ECONNREFUSED, ETIMEDOUT), SSH auth failure ("Permission denied (publickey)"), HTTPS auth failure ("remote: Repository not found", "could not read Username for ..."), remote reference not found ("couldn't find remote ref <branch>"), or git not installed ("ENOENT: no such file or directory, git").Required handlingCaller MUST wrap git.fetch() in try-catch. Minimum handling: try { await git.fetch('origin', 'main'); } catch (err) { if (err instanceof GitError) { console.error('Fetch failed:', err.message); } throw err; } For background sync workers, consider retry with exponential backoff on network errors, but treat auth errors as non-retryable alerts.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - commit · simple-git-commit-missing-try-catcherrorWhenasync function calls git.commit() without wrapping in try-catch. Common failure in CI/CD automation scripts and git-sync workers: if there are no staged changes, git exits non-zero and GitError is thrown. Also fails when git identity (user.name, user.email) is not configured in the environment — common in Docker containers and CI runners.Throws
GitError — on any failure: "nothing to commit" (exit code 1 when nothing staged), "Please tell me who you are" (user.name / user.email not configured), "Aborting commit due to empty commit message" (empty message string), pre-commit hook failure (non-zero exit from .git/hooks/pre-commit).Required handlingCaller MUST wrap git.commit() in try-catch. Pattern for idempotent commit workflows: try { const result = await git.commit('chore: automated update'); if (result.commit) { console.log('Committed:', result.commit); } } catch (err) { if (err instanceof GitError && err.message.includes('nothing to commit')) { console.log('Nothing to commit, skipping'); return; // not a fatal error } throw err; // rethrow unexpected errors } Also configure git identity before committing in CI: await git.addConfig('user.name', 'Bot'); await git.addConfig('user.email', 'bot@example.com');costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - checkout · simple-git-checkout-missing-try-catcherrorWhenasync function calls git.checkout(), git.checkoutBranch(), or git.checkoutLocalBranch() without wrapping in try-catch. Fails when working tree has uncommitted changes that would be overwritten, when the ref/branch does not exist, or when there are file permission issues in the working directory.Throws
GitError — on any failure: "Your local changes to the following files would be overwritten by checkout" (dirty tree), "error: pathspec '...' did not match any file(s) known to git" (ref not found), "A branch named '...' already exists" (checkoutLocalBranch / checkoutBranch on existing), "fatal: reference is not a tree" (invalid commit hash).Required handlingCaller MUST wrap git.checkout() in try-catch. Minimum handling: try { await git.checkout(targetBranch); } catch (err) { if (err instanceof GitError) { if (err.message.includes('would be overwritten')) { // Stash first or abort console.error('Dirty working tree, cannot checkout:', err.message); } } throw err; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - rebase · simple-git-rebase-missing-try-catcherrorWhenasync function calls git.rebase() without wrapping in try-catch. Rebase conflicts leave the repo in a partially-rebased state (detached HEAD, .git/rebase-merge/ directory exists). All subsequent git operations on the same instance fail with "fatal: It seems that there is already a rebase-merge directory, and I wonder if you are in the middle of another rebase." This is the most dangerous uncaught error pattern — it corrupts the working repo state for all future operations.Throws
GitError — on rebase conflict (exit code 1), upstream diverged, or any git failure. Unlike git.merge(), rebase does NOT throw GitResponseError<T> with a structured conflicts array — the conflict details are only in err.message (git stderr output). Common messages: "CONFLICT (content): Merge conflict in <file>", "error: could not apply <sha>... <message>", "Cannot rebase: You have unstaged changes" (dirty tree).Required handlingCaller MUST wrap git.rebase() in try-catch AND abort on failure to restore the repo to a usable state: try { await git.rebase(['origin/main']); } catch (err) { if (err instanceof GitError) { console.error('Rebase failed, aborting:', err.message); // CRITICAL: abort to restore the repo to pre-rebase state try { await git.rebase(['--abort']); } catch (abortErr) { console.error('Rebase abort also failed:', abortErr.message); } } throw err; } Always run rebase on a clean working tree (check git.status() first). Never use git.rebase() in automated workflows without abort handling.costhighin proddegraded serviceusers seeservice unavailablevisibilitysilent - raw · simple-git-raw-missing-try-catcherrorWhenasync function calls git.raw() without wrapping in try-catch. Because raw() runs arbitrary git commands, any git failure propagates. Commonly used for cherry-pick (can fail on conflicts), worktree operations (can fail on path conflicts), bisect, sparse-checkout, and other advanced operations — all of which can fail and need explicit handling.Throws
GitError — when git exits non-zero and printed to stderr. The .message property contains git's stderr output verbatim. Unlike structured methods, raw() never throws GitResponseError — always GitError. Examples: "CONFLICT" from cherry-pick, "fatal: not a git repository", "error: unknown switch `z'" from invalid flags.Required handlingCaller MUST wrap git.raw() in try-catch. Minimum handling: try { const result = await git.raw(['cherry-pick', commitHash]); console.log('Cherry-pick succeeded:', result); } catch (err) { if (err instanceof GitError) { console.error('Git command failed:', err.message); // For cherry-pick, may need: await git.raw(['cherry-pick', '--abort']) } throw err; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - clean · simple-git-clean-missing-try-catcherrorWhenasync function calls git.clean() without wrapping in try-catch. Most common failure: calling git.clean() without the required FORCE or DRY_RUN mode — throws TaskConfigurationError synchronously (before git runs). Also fails when git exits non-zero (permission errors, path issues).Throws
TaskConfigurationError (extends GitError) — thrown immediately (before git runs) when the mode is missing or invalid: 'Git clean mode parameter ("n" or "f") is required', 'Git clean interactive mode is not supported', 'Git clean unknown option found in: ...' GitError — thrown when the underlying git process fails (permission denied, path does not exist, etc.).Required handlingCaller MUST wrap git.clean() in try-catch AND always specify mode: import { simpleGit, CleanOptions } from 'simple-git'; try { // DRY_RUN first to preview what would be deleted const preview = await git.clean(CleanOptions.DRY_RUN + CleanOptions.RECURSIVE); console.log('Would delete:', preview); // Then FORCE to actually delete await git.clean(CleanOptions.FORCE + CleanOptions.RECURSIVE); } catch (err) { if (err instanceof GitError) { console.error('Git clean failed:', err.message); } throw err; } WARNING: git.clean() permanently deletes untracked files. Always preview with CleanOptions.DRY_RUN before running with CleanOptions.FORCE in production automation.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - stash · simple-git-stash-missing-try-catcherrorWhenasync function calls git.stash(), git.stash(['pop']), git.stash(['apply']), or git.stash(['drop']) without wrapping in try-catch. The most dangerous case: git.stash(['pop']) and git.stash(['apply']) can throw GitError when conflicts occur between stashed changes and the current branch — leaving partial conflict markers in the working tree. git.stash() on a clean tree also throws.Throws
GitError — on any failure: "No local changes to save" (stash on clean tree, exit 1), "CONFLICT (content): Merge conflict in <file>" (stash pop/apply conflict, exit 1), "error: Your local changes to the following files would be overwritten by merge" (dirty tree during pop), "fatal: Ref refs/stash is not a stash reference" (drop/apply on empty stash), "ENOENT: no such file or directory, git" (git not installed).Required handlingCaller MUST wrap git.stash() in try-catch for all stash subcommands. For stash save: try { await git.stash(); } catch (err) { if (err instanceof GitError && err.message.includes('No local changes to save')) { // Nothing to stash — not a fatal error return; } throw err; } For stash pop (most important — conflict handling): try { await git.stash(['pop']); } catch (err) { if (err instanceof GitError && err.message.includes('CONFLICT')) { // Stash pop produced conflicts — working tree is dirty // Must resolve conflicts manually or run: git checkout -- . console.error('Stash pop produced conflicts:', err.message); } throw err; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - reset · simple-git-reset-missing-try-catcherrorWhenasync function calls git.reset() or git.reset(ResetMode.HARD) without wrapping in try-catch. Critical failure modes: (1) ResetMode.HARD called programmatically — if anything throws before completion, partially-reset files may be deleted with no undo path. (2) reset() to a specific commit hash fails when the hash does not exist. (3) reset() on a bare repository fails ("fatal: Failed to resolve HEAD"). (4) ResetMode.MERGE fails if there are unmerged paths.Throws
GitError — on any non-zero git exit: "fatal: Failed to resolve 'HEAD'" (empty/bare repo), "fatal: ambiguous argument '<hash>': unknown revision or path" (bad commit reference), "fatal: Cannot do a hard reset in the middle of a merge" (merge in progress), "error: Entry '<file>' not uptodate. Cannot merge" (ResetMode.MERGE with local changes).Required handlingCaller MUST wrap git.reset() in try-catch. For hard reset, always verify the reset target is valid before calling: import { simpleGit, ResetMode } from 'simple-git'; try { // Soft reset: HEAD~1 to uncommit last commit (keeps changes staged) await git.reset(ResetMode.SOFT, ['HEAD~1']); } catch (err) { if (err instanceof GitError) { console.error('Reset failed:', err.message); } throw err; } For HARD reset (DESTRUCTIVE — use with extreme caution): try { await git.reset(ResetMode.HARD); // All uncommitted changes are now permanently gone } catch (err) { // Even HARD reset can fail (e.g., merge in progress) throw err; } NEVER call git.reset(ResetMode.HARD) in automated workflows without: (1) explicit user confirmation or (2) backup of important changes.costhighin prodimmediate exceptionusers seeservice unavailablevisibilitysilent - revert · simple-git-revert-missing-try-catcherrorWhenasync function calls git.revert(commitHash) without wrapping in try-catch. Revert conflicts — where the changes being undone have since been modified — leave the repo in "reverting" state (.git/REVERT_HEAD exists). All subsequent git operations on that working directory fail with "error: you need to resolve your current index first" until the revert is aborted or completed. Also throws when the commit hash is invalid or not found.Throws
GitError — on conflict or invalid ref: "CONFLICT (content): Merge conflict in <file>" (conflict during revert, exit 1), "error: could not revert <hash>" (conflict in fast path), "error: commit <hash> is a merge but no -m option was given" (reverting a merge commit), "fatal: bad object <hash>" (invalid commit hash), "error: you need to resolve your current index first" (revert in progress).Required handlingCaller MUST wrap git.revert() in try-catch AND abort on conflict to restore the working directory to a usable state: try { await git.revert(commitHash); } catch (err) { if (err instanceof GitError && err.message.includes('CONFLICT')) { console.error('Revert produced conflicts, aborting:', err.message); // CRITICAL: abort to clear the reverting state try { await git.revert(['--abort']); } catch (abortErr) { console.error('Revert abort failed:', abortErr.message); } } throw err; } To revert a merge commit, supply the parent option: await git.revert(commitHash, ['-m', '1']);costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - pushTags · simple-git-push-tags-missing-try-catcherrorWhenasync function calls git.pushTags() without wrapping in try-catch. pushTags() is equivalent to push(['--tags']) — all push failure modes apply: network failures, authentication failures, remote rejection (tag already exists with different content), and permission denied.Throws
GitError — on any non-zero exit: "Permission denied (publickey)" (SSH auth failure), "could not read Username for 'https://...'" (HTTPS auth prompt unavailable), "remote: Repository not found" (invalid remote URL), "! [rejected] v1.0.0 -> v1.0.0 (already exists)" (tag already exists), "ECONNREFUSED / ETIMEDOUT" (network failure).Required handlingCaller MUST wrap git.pushTags() in try-catch. Minimum handling: try { await git.pushTags('origin'); } catch (err) { if (err instanceof GitError) { console.error('Tag push failed:', err.message); } throw err; } For release automation, always push tags separately from commits so that failure to push tags does not block the commit push.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - deleteLocalBranch · simple-git-delete-local-branch-missing-try-catcherrorWhenasync function calls git.deleteLocalBranch(branchName) without wrapping in try-catch. The most common failure: deleting a branch with unmerged commits without the forceDelete flag throws GitResponseError<BranchSingleDeleteResult> with the parsed failure details. Also throws when the branch does not exist or when trying to delete the currently checked-out branch.Throws
GitResponseError<BranchSingleDeleteResult> — when the branch has unmerged commits and forceDelete is false (default). The .git property contains the parsed BranchSingleDeleteResult with { branch, hash, success: false }. Error message from git: "error: The branch '<name>' is not fully merged. If you are sure you want to delete it, run 'git branch -D <name>'." GitError — for other failures: "error: branch '<name>' not found" (branch does not exist), "error: Cannot delete branch '<name>' checked out at '<path>'" (current branch).Required handlingCaller MUST wrap git.deleteLocalBranch() in try-catch. Pattern for safe branch deletion: try { const result = await git.deleteLocalBranch(branchName); // result.success is always true here (GitResponseError thrown otherwise) console.log('Deleted branch:', result.branch); } catch (err) { if (err instanceof GitResponseError) { // Unmerged branch — either force-delete or handle gracefully const parsed = err.git; // BranchSingleDeleteResult console.warn('Branch not fully merged:', parsed?.branch); // Option: force delete // await git.deleteLocalBranch(branchName, true); } else if (err instanceof GitError) { console.error('Branch delete failed:', err.message); } throw err; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - submoduleAdd · simple-git-submodule-add-missing-try-catcherrorWhenasync function calls git.submoduleAdd(repo, path) without wrapping in try-catch. submoduleAdd clones the submodule repository over the network — all clone error modes apply. Also fails when the path already exists, when git cannot write .gitmodules, or when the local path conflicts with existing files.Throws
GitError — on any non-zero exit: "fatal: repository '<url>' not found" (invalid remote URL or private repo without auth), "Permission denied (publickey)" (SSH auth failure), "fatal: '<path>' already exists in the index" (path conflict), "fatal: A git repository cannot be used as a subdirectory of itself" (nested git repo), "ECONNREFUSED / ETIMEDOUT" (network failure during submodule clone).Required handlingCaller MUST wrap git.submoduleAdd() in try-catch. Minimum handling: try { await git.submoduleAdd('https://github.com/user/repo.git', 'vendor/repo'); // Remember to stage and commit .gitmodules after adding await git.add('.gitmodules'); await git.commit('Add vendor/repo submodule'); } catch (err) { if (err instanceof GitError) { console.error('Submodule add failed:', err.message); } throw err; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - submoduleUpdate · simple-git-submodule-update-missing-try-catcherrorWhenasync function calls git.submoduleUpdate() or git.submoduleUpdate(['--init']) without wrapping in try-catch. submoduleUpdate fetches each registered submodule from its remote — all fetch/clone failure modes apply. A single failing submodule causes the entire update to fail, leaving other submodules un-updated. Common in post-clone CI scripts that do: await git.submoduleUpdate(['--init', '--recursive']).Throws
GitError — on any non-zero exit: "fatal: repository '<url>' not found" (submodule remote no longer exists), "Permission denied (publickey)" (SSH auth failure for any submodule remote), "fatal: No url found for submodule path '<path>'" (.gitmodules not initialized), "ECONNREFUSED / ETIMEDOUT" (network failure during submodule fetch), "fatal: Needed a single revision" (corrupt submodule reference in parent repo).Required handlingCaller MUST wrap git.submoduleUpdate() in try-catch. Minimum handling (common CI pattern): try { // Initialize + update all submodules recursively await git.submoduleUpdate(['--init', '--recursive']); } catch (err) { if (err instanceof GitError) { console.error('Submodule update failed:', err.message); // Check: are .gitmodules configured? Is the remote accessible? } throw err; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - applyPatch · simple-git-apply-patch-missing-try-catcherrorWhenasync function calls git.applyPatch() without wrapping in try-catch. Common failure: patch context does not match the current working tree (modified base file, line numbers shifted) — git exits non-zero and throws GitError. Also fails when the patch file path is missing or when the patch is malformed.Throws
GitError — on any non-zero exit: "error: patch failed: <file>:<line>" (context mismatch in patch hunk), "error: <file>: patch does not apply" (cannot apply cleanly to current tree), "error: <file>: No such file or directory" (target file missing), "fatal: corrupt patch at line N" (malformed patch input), "error: unrecognized input" (not a valid patch file). TaskConfigurationError (via configurationErrorTask) — thrown when the patches argument is missing or not a string/string[]: "git.applyPatch requires one or more string patches as the first argument".Required handlingCaller MUST wrap git.applyPatch() in try-catch. Minimum handling (apply with rollback): try { await git.applyPatch(['security-fix.patch']); } catch (err) { if (err instanceof GitError) { console.error('Patch apply failed:', err.message); // If partial application happened, restore working tree: // await git.checkout(['--', '.']); // discards unstaged changes } throw err; } For batch-apply workflows, validate each patch with `--check` first: try { await git.applyPatch(patches, ['--check']); // dry-run validate await git.applyPatch(patches); // then apply } catch (err) { throw err; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - mirror · simple-git-mirror-missing-try-catcherrorWhenasync function calls git.mirror() without wrapping in try-catch. mirror() is a clone variant — all clone error modes apply: network errors, SSH/HTTPS authentication failures, "Repository not found", destination path conflicts. Backup workflows that silently swallow these failures create gaps in replication coverage that are not detected until restore.Throws
GitError — on any non-zero exit: "fatal: Repository not found" (invalid remote URL or private repo without auth), "Permission denied (publickey)" (SSH auth failure), "fatal: destination path '<path>' already exists and is not an empty directory" (path conflict), "fatal: could not read Username for 'https://...'" (HTTPS auth prompt unavailable), "fatal: protocol '<x>' is not supported" (invalid URL scheme), "ECONNREFUSED / ETIMEDOUT" (network failure).Required handlingCaller MUST wrap git.mirror() in try-catch. Minimum handling for backup/replication workflows: try { await git.mirror('git@github.com:org/repo.git', '/backup/repo.git'); console.log('Mirror clone succeeded'); } catch (err) { if (err instanceof GitError) { console.error('Mirror clone failed:', err.message); // CRITICAL: backup gap — alert monitoring system // Do not silently swallow — replication SLOs depend on this } throw err; } For scheduled backup jobs, treat mirror failure as a high-severity alert — unreplicated repos are a data-loss exposure.costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - mergeFromTo · simple-git-merge-from-to-missing-try-catcherrorWhenasync function calls git.mergeFromTo() without wrapping in try-catch. Same conflict detection as git.merge() — throws GitResponseError<MergeSummary> when the merge detects conflicts (even with git exit code 0). Also throws TaskConfigurationError if remote/branch arguments are not strings.Throws
GitResponseError<MergeSummary> — when the parser detects merge failed (conflicts in stdout). The .git property contains the parsed MergeSummary with { conflicts: string[], failed: true, ... }. GitError — on git process failure: "fatal: <remote> - not something we can merge" (invalid remote ref), "error: refusing to merge unrelated histories" (no common ancestor), "fatal: You have not concluded your merge" (merge already in progress). TaskConfigurationError (via configurationErrorTask) — thrown if remote or branch arguments are not strings: "Git.mergeFromTo requires that the 'remote' and 'branch' arguments are supplied as strings".Required handlingCaller MUST wrap git.mergeFromTo() in try-catch and handle merge conflicts. Minimum handling: try { await git.mergeFromTo('origin', 'feature-branch'); } catch (err) { if (err.git?.conflicts?.length > 0) { console.error('Merge conflicts:', err.git.conflicts); // Abort to restore clean state: await git.merge(['--abort']); } throw err; } For CI automation flows that automatically merge release branches, always abort on conflict — partial merges block subsequent operations.costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - deleteLocalBranches · simple-git-delete-local-branches-missing-try-catcherrorWhenasync function calls git.deleteLocalBranches() without wrapping in try-catch. Most common failure: one or more branches in the list have unmerged commits and forceDelete is false (default). The task returns a BranchMultiDeleteResult, but partial-failure cases throw GitError when the underlying git process exits non-zero and the deletion error regex matches. Successful cases with partial failures still populate result.errors[] — callers that ignore the return value silently miss undeleted branches.Throws
GitError — when git exits non-zero AND hasBranchDeletionError matches stderr (deleteErrorRegex). Common messages: "error: The branch '<name>' is not fully merged" (unmerged branch), "error: branch '<name>' not found" (branch does not exist), "error: Cannot delete branch '<name>' checked out at '<path>'" (current branch). The parsed result (when delivered via done()) is BranchMultiDeleteResult with .all[], .branches{}, and .errors[] — even success paths may have per-branch failures in .errors that the caller must check.Required handlingCaller MUST wrap git.deleteLocalBranches() in try-catch AND inspect the result.errors[] array for partial failures: try { const result = await git.deleteLocalBranches(['old-feature-1', 'old-feature-2']); if (result.errors.length > 0) { console.warn('Some branches not deleted:', result.errors); // Decide: force-delete the remaining ones, or escalate } console.log('Deleted:', result.all.map(b => b.branch)); } catch (err) { if (err instanceof GitError) { console.error('Bulk branch delete failed:', err.message); } throw err; } For cleanup automation (e.g. delete merged PRs), pass forceDelete=true only after confirming each branch is actually merged via git.branch(['--merged']).costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - checkoutLocalBranch · simple-git-checkout-local-branch-missing-try-catcherrorWhenasync function calls git.checkoutLocalBranch(branchName) without wrapping in try-catch. Most common failure: the branch name already exists locally — git exits non-zero with "fatal: A branch named '...' already exists". Also fails when the working tree has changes that would be overwritten, or when the branch name contains invalid characters (e.g. spaces, colons). Critical in CI release scripts that auto-create branches: a name collision on a previous failed run blocks all subsequent runs until manual cleanup.Throws
GitError — on any non-zero exit: "fatal: A branch named '<name>' already exists" (name collision), "error: pathspec '<name>' did not match any file(s) known to git" (when used as checkout, not -b), "fatal: '<name>' is not a valid branch name" (invalid characters), "error: Your local changes to the following files would be overwritten" (dirty tree).Required handlingCaller MUST wrap git.checkoutLocalBranch() in try-catch. Pattern for idempotent branch creation: try { await git.checkoutLocalBranch('release/v2.0'); } catch (err) { if (err instanceof GitError && err.message.includes('already exists')) { // Branch exists — either reuse it or rotate the name await git.checkout('release/v2.0'); // switch to existing } else { throw err; } } For CI scripts, always use a unique branch name (include timestamp or commit SHA) to avoid collisions on retry: const branchName = `automation/fix-${Date.now()}-${shortSha}`; await git.checkoutLocalBranch(branchName);costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - simpleGit · simple-git-construct-missing-try-catcherrorWhenCode calls simpleGit(baseDir) or simpleGit({ baseDir }) without wrapping in a try-catch. Throws GitConstructError synchronously (not as a Promise rejection) when the baseDir does not exist or cannot be read. Common in production code where baseDir comes from an environment variable, user config, or database value that may point to a non-existent or deleted directory.Throws
GitConstructError extends GitError — thrown SYNCHRONOUSLY from the factory, not as a Promise rejection. Message: "Cannot use simple-git on a directory that does not exist". The .config property contains the options passed to the factory. This error class is exported from simple-git and can be imported: import { GitConstructError } from 'simple-git';Required handlingCaller MUST wrap simpleGit() in try-catch when baseDir is dynamic. Minimum handling: let git: SimpleGit; try { git = simpleGit(repoDir); } catch (err) { if (err instanceof GitConstructError) { console.error(`Git repo directory not found: ${repoDir}`); throw new Error(`Repository directory does not exist: ${repoDir}`); } throw err; } For config-driven repos (environment variables): const repoDir = process.env.REPO_DIR; if (!repoDir) { throw new Error('REPO_DIR environment variable not set'); } try { const git = simpleGit(repoDir); } catch (err) { // GitConstructError: directory doesn't exist throw new Error(`Cannot initialize git at ${repoDir}: ${err.message}`); } DO NOT rely on catching errors from git.push() / git.pull() to detect construction errors — GitConstructError fires before any method call.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - init · simple-git-init-missing-try-catcherrorWhenasync function calls git.init() without wrapping in try-catch. Most common in CLI tooling and scaffold scripts that create repos in paths supplied by the user or from environment variables. Throws GitError when git exits non-zero: permission denied creating .git/, invalid template directory, or when the path has already been initialized as a bare repo and a conflicting init type is requested.Throws
GitError — on any non-zero git exit code. "fatal: cannot mkdir '<path>/.git': Permission denied" — write access denied to the target directory (running as wrong user, read-only filesystem, etc.). "fatal: template directory '<dir>' does not exist or is not a directory" — options.template or --template=<dir> CLI option points to a missing path. Most init failures are terminal: the repository cannot be created, so the caller's downstream git operations (add, commit, push) will all fail.Required handlingCaller MUST wrap git.init() in try-catch when the target directory path is derived from user input, environment variables, or external config. Minimum handling: try { await git.init(); } catch (err) { console.error('Failed to initialize git repository:', err.message); throw err; } For tooling that creates repos in arbitrary directories: try { await git.init(false, { '--initial-branch': 'main' }); } catch (err) { if (err instanceof GitError && err.message.includes('Permission denied')) { throw new Error(`Cannot create repository: permission denied at ${repoPath}`); } throw err; }costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - checkoutLatestTag · simple-git-checkout-latest-tag-missing-try-catcherrorWhenasync function calls git.checkoutLatestTag() without wrapping in try-catch. Composes pull() + tags() + checkout(tags.latest) internally. Any step can throw: pull() throws GitError on network failures, tags() throws GitError when git ls-remote fails, and checkout() throws GitError when tags.latest is undefined (no tags in repo) or when the tag's commit is not reachable. Particularly dangerous because the method silently consumes intermediate errors in its callback chain — if pull() fails, tags() is never called, leaving the repo in an inconsistent state.Throws
GitError — propagated from any of the three composed operations. From pull(): "fatal: couldn't find remote ref" (no upstream), network errors. From checkout(): "error: pathspec 'undefined' did not match any file(s) known to git" — when repository has no tags (tags.latest === undefined). From checkout(): "error: Your local changes to the following files would be overwritten by checkout" — dirty working tree. Note: this is a callback-style legacy method; the Promise it returns resolves/rejects based on the innermost callback's completion.Required handlingCaller MUST wrap git.checkoutLatestTag() in try-catch. Minimum handling: try { await git.checkoutLatestTag(); } catch (err) { console.error('Checkout of latest tag failed:', err.message); throw err; } Guard for empty repos (no tags): const tagList = await git.tags(); if (!tagList.latest) { throw new Error('Repository has no tags to check out'); } try { await git.checkoutLatestTag(); } catch (err) { throw err; } Prefer git.checkout(tags.latest) directly over this method — it's more explicit and avoids the hidden pull() + undefined-tag pitfall.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
Sources
Every postcondition cites at least one of these. Grouped by source type; numbered to match the footnotes above.
Official documentation
- [2]git-scm.com/docs/git-fetchGit Fetch
- [3]git-scm.com/docs/git-commitGit Commit
- [4]git-scm.com/docs/git-checkoutGit Checkout
- [5]git-scm.com/docs/git-rebaseGit Rebase
- [6]git-scm.com/docs/gitGit
- [7]git-scm.com/docs/git-cleanGit Clean
- [8]git-scm.com/docs/git-stashGit Stash
- [9]git-scm.com/docs/git-resetGit Reset
- [10]git-scm.com/docs/git-revertGit Revert
- [11]git-scm.com/docs/git-pushGit Push
- [12]git-scm.com/docs/git-branchGit Branch
- [13]git-scm.com/docs/git-submoduleGit Submodule
- [14]git-scm.com/docs/git-applyGit Apply
- [15]git-scm.com/docs/git-cloneGit Clone
- [16]git-scm.com/docs/git-mergeGit Merge
- [19]git-scm.com/docs/git-initGit Init
Source code
- [1]raw.githubusercontent.com/steveukx/git-js/refssteveukx/git-js · readme.md
- [17]github.com/steveukx/git-js/blobsteveukx/git-js · git-construct-error.ts
- [18]github.com/steveukx/git-js/blobsteveukx/git-js · init.ts
Research notes
Curator notes from SOURCES.md captured when the profile was written so you can verify the reasoning, not just the rules.
Sources — simple-git
All URLs fetched on 2026-04-02.
Official Documentation
-
https://raw.githubusercontent.com/steveukx/git-js/refs/heads/main/simple-git/readme.md
- Error handling patterns: GitError and GitResponseError, try-catch examples
- Merged the summary: "task will reject with a GitError... or a GitResponseError"
-
https://github.com/steveukx/git-js/blob/main/simple-git/typings/simple-git.d.ts
- TypeScript definitions for SimpleGit interface, all async methods
- Error type imports from './errors'
Security
- https://www.codeant.ai/security-research/simple-git-remote-code-execution-cve-2026-28292
- CVE-2026-28292 (CVSS 9.8): RCE via protocol.allow case-sensitivity bypass
- Affected: 3.15.0-3.32.2 | Fixed: 3.32.3
Real-World Evidence
- activepieces/activepieces: packages/server/api/src/app/ee/projects/project-release/git-sync/git-sync-handler.ts
- git.push() called without try-catch (TRUE POSITIVE)
- amplication/amplication: libs/util/git/src/providers/git-cli.ts
- push() wrapper without error handling
- cline/cline: src/core/controller/worktree/checkoutBranch.ts
- Correct try-catch pattern (reference implementation)
- QwenLM/qwen-code: packages/core/src/services/gitService.ts
- Mixed: createFileSnapshot has try-catch, restoreProjectFromSnapshot does not
Need a different package?
Request a profile