Skip to content

fix(desktop): increase git status maxBuffer to prevent stdout overflow#1236

Closed
saddlepaddle wants to merge 1 commit into
mainfrom
fix-git-status-stdout-maxbuffer-exceeded-desktop-1
Closed

fix(desktop): increase git status maxBuffer to prevent stdout overflow#1236
saddlepaddle wants to merge 1 commit into
mainfrom
fix-git-status-stdout-maxbuffer-exceeded-desktop-1

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented Feb 5, 2026

Summary

  • Fixes Sentry issue DESKTOP-1J (2,117 events) where getStatusNoLock crashes with stdout maxBuffer length exceeded
  • Increases maxBuffer from the Node.js default (1 MiB) to 50 MiB for the git status child process
  • Adds automatic fallback from -uall to -unormal if the buffer is still exceeded, which collapses untracked directories into single entries instead of listing every file

Test plan

  • Existing tests pass (bun test — 12 tests in git.test.ts, 43 tests in changes/)
  • Typecheck passes
  • Lint passes
  • Verify on a large repo with many untracked files that status loads without error

Summary by CodeRabbit

  • Bug Fixes
    • Improved stability when working with large git repositories through enhanced buffer management and capacity.
    • Added automatic retry mechanism for git status operations that encounter size-related failures.
    • Enhanced error messages to provide clearer, more user-friendly feedback when git-related issues occur.

Repos with very large numbers of changed/untracked files exceed the
default Node.js maxBuffer (1 MiB) causing ERR_CHILD_PROCESS_STDIO_MAXBUFFER
crashes (Sentry DESKTOP-1J, 2,117 events).

- Set maxBuffer to 50 MiB for the git status execFileAsync call
- Add automatic fallback from -uall to -unormal when buffer is exceeded,
  which collapses untracked directories into single entries
- Extract runGitStatus helper and error-mapping utility for cleaner flow
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 5, 2026

📝 Walkthrough

Walkthrough

This change enhances git status retrieval in a desktop workspace utility by introducing a 50 MiB buffer limit, refactoring the logic into a dedicated helper function, and implementing retry logic with fallback modes when buffer limits are exceeded. It also adds centralized error translation for user-friendly messages.

Changes

Cohort / File(s) Summary
Git Status Refactor & Error Handling
apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts
Added GIT_STATUS_MAX_BUFFER constant (50 MiB), new runGitStatus helper with configurable untracked mode, error detection via isMaxBufferError, error mapping via toGitStatusError, and retry logic in getStatusNoLock that falls back to -unormal mode on buffer overflow.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant getStatusNoLock
    participant runGitStatus
    participant Git as Git Process
    participant ErrorHandler as Error Handler

    Caller->>getStatusNoLock: Request status
    getStatusNoLock->>runGitStatus: Execute with -uall
    runGitStatus->>Git: execFileAsync git status -uall
    
    alt Normal Success
        Git-->>runGitStatus: Status output
        runGitStatus-->>getStatusNoLock: StatusResult
        getStatusNoLock-->>Caller: Parsed status
    else MaxBuffer Exceeded
        Git-->>runGitStatus: ENAMETOOLONG Error
        runGitStatus-->>ErrorHandler: Detect maxBuffer error
        ErrorHandler->>getStatusNoLock: Log warning & retry signal
        getStatusNoLock->>runGitStatus: Execute with -unormal
        runGitStatus->>Git: execFileAsync git status -unormal
        Git-->>runGitStatus: Status output (reduced)
        runGitStatus-->>getStatusNoLock: StatusResult
        getStatusNoLock-->>Caller: Parsed status
    else Other Error
        Git-->>runGitStatus: Other error
        runGitStatus-->>ErrorHandler: toGitStatusError mapping
        ErrorHandler-->>getStatusNoLock: User-friendly error
        getStatusNoLock-->>Caller: Error message
    end
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • superset-sh/superset#739: Modifies the same git utility file and getStatusNoLock function, introducing related error mapping and status retrieval patterns.
  • superset-sh/superset#794: Updates untracked-mode handling and git status execution in the same utility, with overlapping logic for -uall configuration.
  • superset-sh/superset#997: Modifies git status invocation in the same file's getStatusNoLock function with related changes to how git status is called.

Poem

🐰 A buffer overflows with files galore,
So we retry with smaller mode, then explore,
Git status now graceful, with errors so clear,
The rabbit hops forward with nary a fear! 🌙

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: increasing the git status maxBuffer to prevent stdout overflow, which is the core fix for the reported crash.
Description check ✅ Passed The description covers most required sections from the template: provides a clear summary of changes, links a related issue, identifies the type of change (bug fix), includes a test plan with checkboxes, and explains the solution approach comprehensively.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-git-status-stdout-maxbuffer-exceeded-desktop-1

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts`:
- Around line 69-91: The catch currently wraps errors via toGitStatusError
without logging the original; update the block around runGitStatus so the
original error is logged with context before any rethrow or retry: when entering
the catch log the initial error using a prefixed message like "[git/status] git
status failed" along with repoPath, env and the error; if isMaxBufferError
triggers, also log the initial max-buffer error before retrying and then log the
retryError before throwing the wrapped error from toGitStatusError. Use the same
context pattern for all logs and reference the existing symbols runGitStatus,
isMaxBufferError, toGitStatusError, repoPath, env and untrackedMode so the added
logs appear just prior to each throw/retry.
🧹 Nitpick comments (1)
apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts (1)

125-144: Prefer error.code for maxBuffer detection and extract identifiers to constants.

Node.js emits the specific error code ERR_CHILD_PROCESS_STDIO_MAXBUFFER for maxBuffer overflows; checking error.code is more robust than relying on the error message string. Extract the hardcoded identifiers to named constants at the module top to align with the coding guideline on magic strings.

♻️ Suggested patch
 const GIT_STATUS_MAX_BUFFER = 50 * 1024 * 1024;
+const MAX_BUFFER_ERROR_CODE = "ERR_CHILD_PROCESS_STDIO_MAXBUFFER";
+const MAX_BUFFER_ERROR_MESSAGE = "maxBuffer length exceeded";
 
 function isMaxBufferError(error: unknown): boolean {
-	return (
-		error instanceof Error &&
-		error.message.includes("maxBuffer length exceeded")
-	);
+	return (
+		isExecFileException(error) &&
+		(error.code === MAX_BUFFER_ERROR_CODE ||
+			error.message.includes(MAX_BUFFER_ERROR_MESSAGE))
+	);
 }

Comment on lines 69 to +91
try {
// Run git status with --no-optional-locks to avoid holding locks
// Use porcelain=v1 for machine-parseable output, -b for branch info
// Use -z for NUL-terminated output (handles filenames with special chars)
// Use -uall to show individual files in untracked directories (not just the directory)
// Note: porcelain=v1 already includes rename info (R/C status codes) without needing -M
const { stdout } = await execFileAsync(
"git",
[
"--no-optional-locks",
"-C",
repoPath,
"status",
"--porcelain=v1",
"-b",
"-z",
"-uall",
],
{ env, timeout: 30_000 },
);

return parsePortelainStatus(stdout);
return await runGitStatus({ repoPath, env, untrackedMode: "-uall" });
} catch (error) {
// Provide more descriptive error messages
if (isExecFileException(error)) {
if (error.code === "ENOENT") {
throw new Error("Git is not installed or not found in PATH");
}
const stderr = error.stderr || error.message || "";
if (stderr.includes("not a git repository")) {
throw new Error(`Not a git repository: ${repoPath}`);
// If maxBuffer is exceeded even with the generous limit, retry with
// -unormal (collapses untracked directories into a single entry).
if (isMaxBufferError(error)) {
console.warn(
"[git/status] maxBuffer exceeded with -uall, retrying with -unormal:",
repoPath,
);
try {
return await runGitStatus({
repoPath,
env,
untrackedMode: "-unormal",
});
} catch (retryError) {
throw toGitStatusError(retryError);
}
}
throw new Error(
`Failed to get git status: ${error instanceof Error ? error.message : String(error)}`,
);

throw toGitStatusError(error);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Log original git‑status failures before wrapping.

The catch block wraps errors into a new Error without logging the original, which drops diagnostics. Please log with context before throwing so failures aren’t silent in logs.

🔧 Suggested patch
 		if (isMaxBufferError(error)) {
 			console.warn(
 				"[git/status] maxBuffer exceeded with -uall, retrying with -unormal:",
 				repoPath,
 			);
 			try {
 				return await runGitStatus({
 					repoPath,
 					env,
 					untrackedMode: "-unormal",
 				});
 			} catch (retryError) {
+				console.error(
+					"[git/status] git status failed after -unormal retry:",
+					repoPath,
+					retryError,
+				);
 				throw toGitStatusError(retryError);
 			}
 		}
 
+		console.error("[git/status] git status failed:", repoPath, error);
 		throw toGitStatusError(error);
 	}

As per coding guidelines, “Never swallow errors silently; at minimum log errors with context before rethrowing or handling them explicitly” and “Use prefixed console logging with consistent context pattern: [domain/operation] message for entry/exit of significant operations, external API calls, and error conditions”.

🤖 Prompt for AI Agents
In `@apps/desktop/src/lib/trpc/routers/workspaces/utils/git.ts` around lines 69 -
91, The catch currently wraps errors via toGitStatusError without logging the
original; update the block around runGitStatus so the original error is logged
with context before any rethrow or retry: when entering the catch log the
initial error using a prefixed message like "[git/status] git status failed"
along with repoPath, env and the error; if isMaxBufferError triggers, also log
the initial max-buffer error before retrying and then log the retryError before
throwing the wrapped error from toGitStatusError. Use the same context pattern
for all logs and reference the existing symbols runGitStatus, isMaxBufferError,
toGitStatusError, repoPath, env and untrackedMode so the added logs appear just
prior to each throw/retry.

Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite left a comment

Choose a reason for hiding this comment

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

Review: fix(desktop): increase git status maxBuffer to prevent stdout overflow

Verdict: Approve — clean, well-structured fix for a real crash (2,117 Sentry events).

What's good

  • Refactoring is solid: Extracting runGitStatus as a helper with a typed untrackedMode param ("-uall" | "-unormal") is clean and follows the project's object-parameter convention for 2+ args.
  • Fallback strategy is smart: Retrying with -unormal (collapses untracked dirs) is a graceful degradation — users still get status info, just less granular for untracked files.
  • Buffer size is reasonable: 50 MiB is generous but appropriate for very large monorepos. Git status output is transient, so this won't cause sustained memory pressure.
  • Error centralization: toGitStatusError keeps the error-mapping logic DRY between the initial attempt and retry path.
  • Logging follows conventions: [git/status] prefix with console.warn per the codebase logging patterns.

Minor observations (non-blocking)

  1. isMaxBufferError string matching vs error code: CodeRabbit flagged this too — Node.js sets error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" on these errors, which is more robust than substring matching on error.message. The current approach will work fine in practice, but checking code first (with message as fallback) would be slightly more defensive. Up to you whether to address.

  2. repoPath dropped from "Not a git repository" error: The original code had Not a git repository: ${repoPath}, but toGitStatusError returns Not a git repository without the path. This loses some diagnostic context. Consider adding the path back — you could either pass repoPath into toGitStatusError, or have the caller append it.

  3. No new tests for the new helpers: isMaxBufferError, toGitStatusError, and the retry logic are all simple enough that they don't strictly need tests, but unit tests for the error detection and mapping would strengthen confidence (especially since these are the core of the fix). Worth considering for a follow-up.

Overall this is a clean, targeted fix. Nice work.

Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite left a comment

Choose a reason for hiding this comment

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

Review: fix(desktop): increase git status maxBuffer to prevent stdout overflow

Verdict: Approve — solid bug fix with a well-designed fallback mechanism.

What this PR does

  • Increases maxBuffer from Node.js default (1 MiB) to 50 MiB for git status in getStatusNoLock
  • Adds automatic retry: if -uall exceeds the buffer, falls back to -unormal (collapses untracked directories into single entries)
  • Extracts runGitStatus, isMaxBufferError, and toGitStatusError helpers for cleaner code

Strengths

  • Correct fix for the root cause — 2,117 Sentry events from ERR_CHILD_PROCESS_STDIO_MAXBUFFER is a real problem in large repos
  • Smart fallback strategy-uall-unormal gracefully degrades instead of crashing, and the console.warn provides visibility
  • Clean refactoringrunGitStatus uses typed untrackedMode: "-uall" | "-unormal" and follows the project's object-params convention
  • Buffer size is appropriate — 50 MiB is generous but reasonable; even monorepos with 100k+ changed files would fit within this
  • CI green — build, lint, test, typecheck all pass

Minor suggestions (non-blocking)

  1. isMaxBufferError could also check error.code — Node.js sets error.code to "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" for this specific error, which is more reliable than string matching on error.message. Consider:

    function isMaxBufferError(error: unknown): boolean {
      if (!(error instanceof Error)) return false;
      return (
        (error as any).code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" ||
        error.message.includes("maxBuffer length exceeded")
      );
    }

    Checking both provides maximum compatibility across Node versions.

  2. toGitStatusError drops repoPath from "Not a git repository" error — The original code included repoPath in this error message (Not a git repository: ${repoPath}), which is helpful for debugging. Since toGitStatusError doesn't receive repoPath, this context is lost. Consider passing repoPath to toGitStatusError or appending it at the call site.

Both are minor — the fix is correct and well-structured as-is.

Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite left a comment

Choose a reason for hiding this comment

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

Review: Approve

Clean, well-scoped fix for a real production issue (Sentry DESKTOP-1J, 2,117 events).

What's good

  • Correct fix: Increasing maxBuffer from Node.js default (1 MiB) to 50 MiB addresses the root cause. 50 MiB is generous but reasonable as a ceiling for git status output on large monorepos.
  • Graceful degradation: The fallback from -uall to -unormal when the buffer is still exceeded is a smart tradeoff — collapses untracked directories into single entries rather than crashing entirely.
  • Clean refactoring: Extraction of runGitStatus, isMaxBufferError, and toGitStatusError helpers keeps getStatusNoLock readable. The typed untrackedMode: "-uall" | "-unormal" union is a nice touch.
  • Follows project conventions: Object parameter style, [git/status] log prefix, named constant with doc comment.

Minor note (non-blocking)

toGitStatusError returns new Error("Not a git repository") without including repoPath, while the original had `Not a git repository: ${repoPath}`. This is a small loss of debugging context. Consider threading repoPath through if it's useful for production diagnostics, but not a blocker.

LGTM — ship it.

Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite left a comment

Choose a reason for hiding this comment

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

Review: fix(desktop): increase git status maxBuffer to prevent stdout overflow

Verdict: Approve — solid, well-structured bug fix for a real crash (2,117 Sentry events).

What's good

  • Smart fallback strategy: -uall-unormal on buffer overflow is the right degradation — users get slightly less granular untracked file info instead of a crash.
  • Clean refactoring: Extracting runGitStatus, isMaxBufferError, and toGitStatusError keeps getStatusNoLock readable and each helper single-purpose.
  • Follows project conventions: Object params for 2+ args in runGitStatus, [git/status] log prefix, typed union for untrackedMode.
  • 50 MiB is reasonable: Large monorepos with git status -uall can produce 10-15 MB; 50 MiB gives plenty of headroom without memory concerns.

Minor suggestions (non-blocking)

  1. repoPath dropped from "Not a git repository" errortoGitStatusError returns new Error("Not a git repository") but the original included the path: new Error(`Not a git repository: ${repoPath}`). Consider threading repoPath into toGitStatusError to preserve this diagnostic context, or passing it as context to the error message. This helps when debugging issues across multiple repositories/worktrees.

  2. isMaxBufferError string matching — CodeRabbit flagged this too. Node.js sets error.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" on these errors. Checking the error code is more robust than string matching on error.message. A belt-and-suspenders approach:

    function isMaxBufferError(error: unknown): boolean {
      if (!(error instanceof Error)) return false;
      return (
        ("code" in error && (error as any).code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") ||
        error.message.includes("maxBuffer length exceeded")
      );
    }

    Not a big deal since the string approach works, but worth considering for resilience across Node versions.

  3. No tests for the new fallback path — The existing 12 tests pass, but none exercise the maxBuffer→retry behavior. A test that mocks execFileAsync to throw a maxBuffer error on the first call and succeed on the second would give confidence the retry works correctly. Not blocking, but would be a nice follow-up.

Overall, clean fix that addresses a real user-facing crash with a well-thought-out fallback. Ship it!

Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite left a comment

Choose a reason for hiding this comment

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

Review: LGTM with minor suggestions

Good fix for DESKTOP-1J (2,117 events). The approach is well-reasoned:

  • 50 MiB buffer ceiling is generous but appropriate — repos with massive node_modules or generated files easily exceed 1 MiB.
  • Graceful fallback from -uall to -unormal is a smart degradation strategy. Collapsing untracked directories is a reasonable trade-off vs crashing.
  • Extracted helpers (runGitStatus, isMaxBufferError, toGitStatusError) clean up the code nicely. runGitStatus properly uses the object-params convention.
  • Logging follows [domain/operation] convention.

Minor suggestions (non-blocking)

  1. Dropped repoPath from error messagetoGitStatusError returns new Error("Not a git repository") but the original included the path: Not a git repository: ${repoPath}. Since toGitStatusError doesn't have access to repoPath, this is an acceptable trade-off, but worth noting that diagnostic context was lost. Could pass repoPath into toGitStatusError if you think it's valuable.

  2. Error detection could use error codeisMaxBufferError checks error.message.includes("maxBuffer length exceeded"). This works, but checking (error as any).code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER' would be more robust against Node.js message wording changes in future versions. Very minor nit.

Both are non-blocking. Ship it.

@Kitenite Kitenite deleted the fix-git-status-stdout-maxbuffer-exceeded-desktop-1 branch March 15, 2026 16:08
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.

2 participants