Skip to content

feat(git): friendly checkout error messages with stash & switch recovery#1785

Open
Marve10s wants to merge 10 commits intopingdotgg:mainfrom
Marve10s:feat/checkout-dirty-worktree-error-handling
Open

feat(git): friendly checkout error messages with stash & switch recovery#1785
Marve10s wants to merge 10 commits intopingdotgg:mainfrom
Marve10s:feat/checkout-dirty-worktree-error-handling

Conversation

@Marve10s
Copy link
Copy Markdown
Contributor

@Marve10s Marve10s commented Apr 6, 2026

Summary

  • When git checkout fails because the working tree has uncommitted changes, the app now shows a friendly "Uncommitted changes block checkout" error toast listing the conflicting files — instead of dumping a raw GitCommandError stack trace with internal file paths
  • The error toast includes a "Stash & Switch" action button that stashes changes, checks out the target branch, and attempts to pop the stash on the new branch
  • If the stash cannot be applied cleanly (e.g. the target branch doesn't have the same files), the server resets the merge state, cleans up orphaned untracked files, and keeps the stash entry safe. The UI shows a warning toast with a "Discard stash" button (with confirmation dialog) to drop the stash
  • Both "Your local changes would be overwritten" and "untracked working tree files would be overwritten" git error variants are now detected and handled with the same friendly UX
  • The "you need to resolve your current index" error (which previously showed a raw stack trace) now shows a clear "Unresolved conflicts in the repository" message
  • Toast layout restructured: copy and action buttons now render below the description instead of floating inline with the title. Copy button extended to warning-type toasts
  • Adds GitCheckoutDirtyWorktreeError tagged error, stashAndCheckout and stashDrop WS methods end-to-end (contracts → server → client)

Note

Medium Risk
Changes branch checkout error handling and adds new stash-based recovery commands end-to-end (contracts, server GitCore, WS RPC, and UI), which can affect git state and error propagation. Risk is mitigated by extensive new integration tests covering conflict and cleanup scenarios.

Overview
Improves branch switching UX when git checkout is blocked by a dirty worktree. checkoutBranch now detects “would be overwritten” (tracked and untracked) failures and returns a typed GitCheckoutDirtyWorktreeError containing the target branch and conflicting files.

Adds a new “stash & switch” recovery path via GitCore.stashAndCheckout (stash with a descriptive message, checkout, then pop), including cleanup logic when stash pop conflicts and a stashDrop helper to discard the top stash entry.

Exposes the new operations over WebSocket RPC (gitStashAndCheckout, gitStashDrop) and updates the branch selector to show friendly toasts with action buttons (including “Stash & Switch” and optional “Discard stash”), plus a clearer message for unresolved index errors. Toast rendering is adjusted so copy/action buttons appear below the description and copy is available for warnings too.

Reviewed by Cursor Bugbot for commit 8d9f042. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add stash-and-switch checkout recovery with friendly dirty worktree error messages

  • Introduces a structured GitCheckoutDirtyWorktreeError (with branch, cwd, and conflictingFiles fields) when checkoutBranch is blocked by uncommitted or untracked file conflicts, replacing the generic GitCommandError.
  • Adds stashAndCheckout to the git service: stashes local changes, checks out the target branch, and pops the stash; on failure it restores state, cleans untracked stash remnants, and preserves the stash with a descriptive error.
  • Adds stashDrop to drop the top stash entry via the API.
  • The branch selector in BranchToolbarBranchSelector.tsx now shows actionable toasts on dirty worktree failures, offering a 'Stash & Switch' action and an optional 'Discard stash' follow-up.
  • Toast layout updated in toast.tsx: action and copy buttons now appear beneath the description, and warnings also show the copy button.
  • Behavioral Change: WsGitCheckoutRpc error schema now includes GitCheckoutDirtyWorktreeError in its union; callers handling GitManagerServiceError may now receive this new error type.

Macroscope summarized 8d9f042.

@github-actions github-actions bot added size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Apr 6, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 6, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2db636bf-28a1-4f0b-bb43-836f332fe5cc

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@Marve10s Marve10s force-pushed the feat/checkout-dirty-worktree-error-handling branch 2 times, most recently from 0ed6af8 to 6709644 Compare April 6, 2026 17:38
@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp bot commented Apr 6, 2026

Approvability

Verdict: Needs human review

This PR introduces a new feature for git checkout error recovery with stash & switch functionality. It adds new git operations, RPC methods, error types, and complex UI error handling flows with actionable toasts. The scope and runtime behavior changes warrant human review. Additionally, there's an unresolved comment about potential UI flicker from the state management refactor.

You can customize Macroscope's approvability policy. Learn more.

@Marve10s Marve10s force-pushed the feat/checkout-dirty-worktree-error-handling branch 2 times, most recently from ebe2610 to 0ff3bdc Compare April 6, 2026 17:47
@Marve10s Marve10s force-pushed the feat/checkout-dirty-worktree-error-handling branch 2 times, most recently from 0252c65 to 042a9d9 Compare April 6, 2026 18:12
@Marve10s
Copy link
Copy Markdown
Contributor Author

Marve10s commented Apr 6, 2026

Before :
before-1-dirty-checkout-raw-error
before-2-untracked-raw-error

After :
after-1-friendly-error-with-stash-button
after-2-stash-conflict-warning-with-discard
If users tried to discard stash - shows a modal with approval.

Replace raw GitCommandError stack traces with structured, user-friendly
error handling when branch checkout fails due to uncommitted changes.
@Marve10s Marve10s force-pushed the feat/checkout-dirty-worktree-error-handling branch from 042a9d9 to c3cac0a Compare April 6, 2026 18:17
@sashaduke
Copy link
Copy Markdown

+1, I have an error of a different kind that spills out so much i cant even close the toast so im gonna make a similar PR but this is useful

Resolve merge conflicts:
- BranchToolbarBranchSelector.tsx: adapt imports to LocalApi/EnvironmentApi split
- ipc.ts: move stashAndCheckout/stashDrop to EnvironmentApi.git (not LocalApi)
- wsNativeApi.ts: accept upstream deletion (replaced by localApi.ts + environmentApi.ts)
@github-actions github-actions bot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Apr 9, 2026
- Add stashAndCheckout/stashDrop to EnvironmentApi bindings and RPC wiring
- Replace useOptimistic with useState to work without useTransition
- Wrap toast actions in runBranchAction for concurrency safety
- Pass environmentId through checkout error context for query invalidation
- Add missing Cause import for GitCore stash error handling
- Use --include-untracked in stash cleanup to capture all remnants
@Marve10s Marve10s force-pushed the feat/checkout-dirty-worktree-error-handling branch from 3662ac5 to e3db0ea Compare April 9, 2026 15:08
Resolve merge conflicts:
- GitCore.ts service: combine Context import with new Scope type import
- ws.ts: take upstream's refactored replayEvents handler, add new
  gitStashAndCheckout and gitStashDrop method registrations
- BranchToolbarBranchSelector.tsx: combine PR imports (EnvironmentApi,
  QueryClient, invalidateGitQueries, readLocalApi) with upstream's new
  imports (scopeProjectRef, scopeThreadRef, ThreadId, DraftId, etc.)
…tent dirty worktree errors

- Add refreshGitStatus(input.cwd) to the gitStashDrop WS handler so
  clients see updated stash state after a drop, matching all other
  git mutation handlers.
- Detect when stashAndCheckout fails because ignored files still
  conflict (stash -u doesn't capture .gitignore'd files) and show
  an actionable error instead of a confusing dirty-worktree message.
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 8d9f042. Configure here.

const [resolvedActiveBranch, setOptimisticBranch] = useState(canonicalActiveBranch);
useEffect(() => {
setOptimisticBranch(canonicalActiveBranch);
}, [canonicalActiveBranch]);
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.

useState+useEffect sync overrides in-flight optimistic branch value

Medium Severity

Replacing useOptimistic/useTransition with useState + useEffect changes the synchronization semantics. The useEffect syncs resolvedActiveBranch to canonicalActiveBranch on every change — including while a branch action is in flight. If a git status refresh fires mid-checkout and reports the old branch, the useEffect overwrites the optimistic value, causing the branch display to flicker back to the old name before settling on the new one. The previous useOptimistic implementation preserved the optimistic value for the duration of the transition, avoiding this flicker.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8d9f042. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants