Skip to content

fix(renderer): sanitize persisted snapshots#2199

Open
janburzinski wants to merge 3 commits into
mainfrom
emdash/error-creating-workspace-lt2e9
Open

fix(renderer): sanitize persisted snapshots#2199
janburzinski wants to merge 3 commits into
mainfrom
emdash/error-creating-workspace-lt2e9

Conversation

@janburzinski
Copy link
Copy Markdown
Collaborator

summary

  • workspace creation failed on existing folders
  • git failed before provisioning finished

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 23, 2026

Greptile Summary

This PR fixes two independent issues: worktree creation failing when the canonical pool path is a stale non-worktree directory, and snapshot persistence crashing when MobX state contains non-serializable values.

  • Worktree service: Replaces the old delete-and-retry pattern with resolveAvailableTargetPath, which walks suffixed paths (-2, -3, …) to find the first free slot or the slot already checked out for the requested branch. getWorktree is simplified to scan the git worktree list directly, dropping the now-redundant stale-path deletion. Three integration tests cover the stale-directory, suffix-collision, and checkoutExistingBranch stale-directory scenarios.
  • Snapshot registry: Wraps every snapshot in structuredClone(toJS(snapshot)) before feeding it to the MobX reaction and the view-state cache. Failures are caught, logged, and return undefined, which is guarded against before any IPC call.

Confidence Score: 5/5

Safe to merge — both fixes are well-scoped, the stale-directory path is preserved rather than deleted, and three integration tests exercise the new suffix-resolution logic end-to-end.

The worktree suffix logic is straightforward and the regression tests directly cover the two reported failure modes (stale canonical path and suffix-collision with a different branch). The snapshot-registry change is defensive — it adds a try/catch around the clone and short-circuits on undefined, so the only behavioral change is that previously-crashing serializations now silently skip. No data loss, no silent misbehaviour in the success path.

No files require special attention.

Important Files Changed

Filename Overview
src/main/core/projects/worktrees/worktree-service.ts Adds resolveAvailableTargetPath to handle stale directories and suffix-path collisions, replaces the old delete-and-retry pattern, and simplifies getWorktree to rely entirely on the git worktree list.
src/main/core/projects/worktrees/worktree-service.test.ts Adds three regression tests covering: stale-directory suffix fallback for checkoutBranchWorktree, the foo/foo-2 suffix-collision case, and stale-directory suffix fallback for checkoutExistingBranch.
src/renderer/lib/stores/snapshot-registry.ts Introduces toCloneableSnapshot (structuredClone + toJS) to sanitize MobX observables before persisting; uncloneable snapshots are logged and silently skipped instead of causing IPC serialization errors.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[checkoutBranchWorktree / checkoutExistingBranch] --> B[findBranchAnywhere]
    B -->|found valid worktree| C[return ok - existing path]
    B -->|not found| D[targetPath = poolPath / branchName]
    D --> E[resolveAvailableTargetPath]
    E --> F{targetPath exists?}
    F -->|No| G[return targetPath]
    F -->|Yes| H{isValidWorktree AND branch matches?}
    H -->|Yes| G
    H -->|No| I[try suffix i=2..999]
    I --> J{candidate exists?}
    J -->|No| K[return candidate]
    J -->|Yes| L{isValidWorktree AND branch matches?}
    L -->|Yes| K
    L -->|No| M[i++]
    M --> I
    G --> N{isValidWorktree at resolved path?}
    K --> N
    N -->|Yes| C
    N -->|No| O[git worktree add resolved path branchName]
    O --> P[return ok - new path]
Loading

Reviews (2): Last reviewed commit: "fix(worktrees): validate suffixed branch..." | Re-trigger Greptile

Comment on lines +79 to +90
private async resolveAvailableTargetPath(targetPath: string): Promise<string> {
if (!(await this.targetPathExists(targetPath))) return targetPath;
if (await this.isValidWorktree(targetPath)) return targetPath;

for (let i = 2; i < 1000; i++) {
const candidate = `${targetPath}-${i}`;
if (!(await this.targetPathExists(candidate))) return candidate;
if (await this.isValidWorktree(candidate)) return candidate;
}

throw new Error(`No available worktree path found for ${targetPath}`);
}
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.

P1 Wrong branch returned via suffix collision

When branch foo can't use its primary pool path (stale dir at poolPath/foo), resolveAvailableTargetPath tries poolPath/foo-2. If a separate branch literally named foo-2 already has a valid worktree at poolPath/foo-2, the function returns that path unchanged — and the callers (doCheckoutBranchWorktree, doCheckoutExistingBranch) immediately return ok(poolPath/foo-2) without verifying that the worktree there actually corresponds to foo. Tasks for branch foo then run against branch foo-2's working tree, silently mixing their content.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/main/core/projects/worktrees/worktree-service.ts
Line: 79-90

Comment:
**Wrong branch returned via suffix collision**

When branch `foo` can't use its primary pool path (stale dir at `poolPath/foo`), `resolveAvailableTargetPath` tries `poolPath/foo-2`. If a separate branch literally named `foo-2` already has a valid worktree at `poolPath/foo-2`, the function returns that path unchanged — and the callers (`doCheckoutBranchWorktree`, `doCheckoutExistingBranch`) immediately return `ok(poolPath/foo-2)` without verifying that the worktree there actually corresponds to `foo`. Tasks for branch `foo` then run against branch `foo-2`'s working tree, silently mixing their content.

How can I resolve this? If you propose a fix, please make it concise.

@janburzinski
Copy link
Copy Markdown
Collaborator Author

janburzinski commented May 23, 2026

@greptileai

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.

1 participant