Recover threads whose checkout folder was deleted - #161
Merged
Conversation
A thread running in a git worktree broke permanently when that folder was removed: the next turn failed inside the provider SDK as "Claude Code native binary not found", the only affordance was a Retry that failed identically, new threads inherited the dead path, and the source panel offered to run `git init` on a directory that was not there. Detection and recovery: - Pre-flight the checkout at the single seam every provider start, restart and handoff passes through (ProviderCommandReactor). A confirmed-missing directory fails with CheckoutMissingError and records a thread.checkout.missing activity; no process is ever spawned in a directory that is gone. - Classify spawn ENOENT honestly. A missing cwd and a missing executable fail with the same errno, and the SDK wraps it as a binary problem. A confirmed missing working directory now outranks that guess. - Offer the two things that resolve it, in the composer notice and the source control panel: move the thread to the project root, or recreate the worktree at the same path (only when its branch still exists). Fallbacks so one broken thread stays one broken thread: - Drafts no longer inherit a checkout already known to be missing. - The checkout picker survives an invalid selection: it stays visible while the project root is a repository, marks the selection "(missing)", and lists refs from the project root so there is always a way out. - VCS status distinguishes "path does not exist" from "not a repository", so Initialize Git is only offered for a directory that is actually there. Guardrails: - vcs.removeWorktree refuses a path a non-stopped session or a live thread still uses, failing with VcsWorktreeInUseError naming them. No force override. - The status broadcaster polls watched checkouts and reports a disappearance after a confirmation window, so recovery appears between turns rather than after the next crash. - Sessions running in a managed worktree are told not to remove or prune it. The missing-checkout determination lives in one place (vcs/CheckoutPresence), shared by the pre-flight, the status typing and the watcher. A check error is never treated as missing: only a confirmed absence opens the new paths.
The thread view decides whether to show the recovery actions from the checkout's VCS status, which can still be a healthy snapshot taken before the folder was deleted. The pre-flight now refreshes that status when it finds a checkout missing, so the affordance appears with the failed turn instead of waiting for the watcher's next pass. Test fallout from the new behaviour, all real rather than cosmetic: - GitWorkflowService stats the requested cwd now, so its test layers need a filesystem, and the two "no repository detected" cases were pointed at a path that does not exist. They now use a directory that is really there, and a new case pins the distinction that Initialize Git depends on. - The reactor and integration harnesses stub the status broadcaster and needed the new missing-checkout stream. - The reactor suite's project root was an invented path, which the pre-flight correctly refuses; it now uses real temp directories. - Four browser suites stub gitStatusState with a partial factory that the draft seeder's new synchronous snapshot read was missing.
…reate, honest launcher rows A full Playwright pass against a throwaway stack (delete the worktree out from under a bound thread, recover both ways, navigate around) surfaced four defects the unit suites could not: - Status cache/stream keys now canonicalize through the nearest existing ancestor, so a subscription opened while the checkout is missing keys identically to the statuses published after it is recreated. realPath alone flips form across the existence boundary (/tmp vs /private/tmp on macOS), which stranded the recovery UI after any navigation. - Recreating a worktree over the stale registration a deleted folder leaves behind now prunes and retries; git otherwise refuses the path. - The presence watcher pushes a fresh status when a missing checkout reappears, so recovery surfaces clear in seconds instead of at the next scheduled refresh. - Local status parts keep pathMissing across remote updates; the checkout picker labels the thread's own branch rather than the fallback ref list's; warning activities are excluded from timeline summarization (the keyword heuristic read "folder" and filed the notice as exploration); the panel launcher rows say the folder is missing instead of describing a tree that is not there.
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
… stubs Caught by CI's cold typecheck: the worktree's vp cache replayed a stale typecheck success locally, so three errors shipped — the Codex adapter now provides the FileSystem service the session runtime requires for its managed-worktree check, and two reactor test stubs gained the broadcaster's new observeMissingCheckouts method.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the three failure modes from the deleted-worktree incident: a thread whose git worktree vanished broke with a misleading "Claude binary not found" error and a Retry that could never work, new draft threads inherited the dead folder with the checkout picker hidden (bricking the project), and nothing prevented or detected the deletion in the first place.
What changes
Detection and recovery
vcs/CheckoutPresence.ts) answers "is this checkout still on disk?" for every consumer, with a three-state design: only a confirmed absence triggers recovery; a check error always fails open.CheckoutMissingErrorinstead of reaching the SDK. Spawn ENOENT against a confirmed-missing cwd is reclassified honestly (the SDK wraps the error and drops the errno, so the directory check decides).No more inherited traps
pathMissing) from "not a repository", so the source panel shows recovery actions instead of offeringgit initon a deleted folder, and the panel launcher rows say the folder is missing instead of describing a tree that is not there.Guardrails
vcs.removeWorktreerefuses when a live session or an active thread still uses the worktree, naming the blocking threads (typedVcsWorktreeInUseError, no force flag). Archived threads do not block; a projection read failure fails open.Plumbing fixes found by live verification
realPathalone flips between/tmpand/private/tmpforms across the existence boundary and stranded the recovery UI after navigation).pathMissingacross remote updates (it was silently dropped, collapsing the recovery UI moments after it appeared).Testing
vp fmt,vp lint,vp run typecheck, full server (1878) and web (2222) unit suites, browser suite (471).it.livewith symlinked temp dirs), stale-registration recreate against real git,pathMissingretention across remote updates, draft fallback, picker survival, launcher missing states, recovery view-model.