Skip to content

fix(host-service): decide workspace-delete cleanup on git state, not error text#5138

Merged
saddlepaddle merged 2 commits into
mainfrom
mini-wolfsbane
Jun 5, 2026
Merged

fix(host-service): decide workspace-delete cleanup on git state, not error text#5138
saddlepaddle merged 2 commits into
mainfrom
mini-wolfsbane

Conversation

@saddlepaddle

@saddlepaddle saddlepaddle commented Jun 5, 2026

Copy link
Copy Markdown
Collaborator

Problem

Users have been hitting workspace-delete failures since #4801's cleanup reorder, in two distinct shapes:

  1. Hard failuresFailed to remove worktree at … thrown errors that abort the whole delete. [codex] Fix worktree delete cleanup ordering #4801 converted worktree-removal failure from a best-effort warning into a hard throw and dropped fix(host-service): tolerate locked+missing worktrees in destroy #4693's tolerant substring matcher, leaving only existsSync as the success signal. Any worktree state where the directory still exists but git worktree remove errors (locked, or — common on macOS — git registered the worktree under a realpath-resolved path like /private/var/... while the stored path is /var/...) now fails the delete outright.
  2. Scary-but-harmless warningsFailed to delete branch '…' not found toasts on deletes that actually succeeded (cloud row already gone, worktree already removed). The branch ref was simply already absent — which is exactly what deleteBranch is trying to achieve.

Both came from deciding cleanup success by inspecting git's error text, which is locale- and version-dependent and brittle by nature (the reason #4801 removed the matcher in the first place).

Fix

Decide on git's actual state, never on error strings:

  • Worktree removal — attempt worktree remove --force --force against the canonical (realpath) path so a symlinked stored path still matches its registration, then consult git worktree list --porcelain. Continue if the path is no longer registered; throw only if git still tracks a live worktree we genuinely couldn't remove (preserves [codex] Fix worktree delete cleanup ordering #4801's no-orphan-disk guarantee). No blanket worktree prune--force --force already unregisters a missing-dir worktree, and a global prune would clobber other workspaces' stale metadata (guarded by an existing integration test).
  • Branch delete — check existence with git show-ref --verify --quiet (exit-code based). An absent ref → success, no warning. A ref that exists but fails -D → still warns.

Reuses the canonical listGitWorktrees / normalizeWorktreePath helpers (the latter already handles the /var/private/var realpath canonicalization).

Tests

  • Updated the makeCtx unit mock to model the two new git reads (worktree list, show-ref) and reworked the worktree-failure test to express failure as a still-registered worktree rather than a thrown remove.
  • bun test for the 5 cleanup-touching files: 70/70 pass. Lint clean, typecheck exit 0.

Verified against prod

Reproduced the reported case (Roshvan's Roshvan/eat-147-journey-transparency): the v2 cloud row was already gone, confirming the delete had committed and only the cosmetic branch warning was left. After this change that path returns { success: true, branchDeleted: true, warnings: [] } — the user just sees the workspace disappear, no warning toast.


Open in Stage

Summary by cubic

Fixes workspace delete failures by deciding cleanup based on git state instead of parsing error text. Also guards against git read failures masquerading as success.

  • Bug Fixes
    • Worktree: run git worktree remove --force --force on the canonical path, then verify with git worktree list --porcelain via git.raw. If the list read fails, treat state as unknown and block; continue only if unregistered. Throw if still registered. No global prune.
    • Branch: check existence with git branch --list. Missing ref counts as success; warn only on a real git failure or a -D failure when the ref exists.
    • Tests: add mocks for worktree list and branch --list; update failure case to assert on a still-registered worktree.

Written for commit f16641c. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • More reliable workspace cleanup: worktree paths are canonicalized and removals are verified against Git's registry before proceeding.
    • Branch deletion now checks for local existence first and treats already-missing branches as successful removals.
    • Cleanup steps now block subsequent cloud deletion until Git confirms worktree removal.
  • Tests

    • Updated tests to simulate Git worktree listings and branch presence, ensuring failure-handling and ordering are validated.

…error text

Workspace deletes were failing for users in two ways after the #4801
cleanup reorder: hard "Failed to remove worktree" errors that aborted the
delete, and scary "Failed to delete branch ... not found" warnings on
deletes that had actually succeeded.

Both steps now branch on git's real state instead of parsing locale- and
version-dependent error strings:

- Worktree removal: run `worktree remove --force --force` against the
  canonical (realpath) path so a symlinked stored path (macOS
  /var -> /private/var) still matches its registration, then check
  `git worktree list --porcelain`. Continue if the path is no longer
  registered; throw only if git still tracks a live worktree we couldn't
  remove (preserving the no-orphan-disk guarantee). No blanket
  `worktree prune` -- `--force --force` already unregisters a missing-dir
  worktree, and a global prune would clobber other workspaces' stale
  metadata.
- Branch delete: check existence with `git show-ref --verify --quiet`.
  An absent ref is the goal of deleteBranch, so treat it as success
  instead of surfacing a warning. A ref that exists but fails `-D` still
  warns.

Unit mock updated to model the new `worktree list` / `show-ref` reads.
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 13a32e8f-3e4f-4db7-851d-3c9b25cdd4d3

📥 Commits

Reviewing files that changed from the base of the PR and between 0eabd56 and f16641c.

📒 Files selected for processing (2)
  • packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts
  • packages/host-service/test/workspace-cleanup.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts
  • packages/host-service/test/workspace-cleanup.test.ts

📝 Walkthrough

Walkthrough

Workspace cleanup now adds git path normalization and explicit state verification to make local workspace cleanup deterministic. Worktree removal computes canonical paths to handle symlinks, then verifies removal by querying git's worktree registry; branch deletion first checks branch existence before attempting removal.

Changes

Deterministic Workspace Cleanup

Layer / File(s) Summary
Helper functions for git state verification
packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts
Adds imports and two helpers isRegisteredWorktree and localBranchExists to canonicalize worktree paths and interpret git worktree list --porcelain and git branch --list outputs.
Deterministic worktree removal with canonical paths
packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts
Worktree removal canonicalizes the path, runs git worktree remove --force --force against it, then verifies removal by checking git's worktree registry; if git still registers the worktree, the function throws INTERNAL_SERVER_ERROR.
Branch deletion with pre-existence check
packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts
Branch deletion now checks git branch --list for the ref and skips deletion when absent; otherwise it runs git branch -D and records any failure as a warning.
Test mock and validation updates
packages/host-service/test/workspace-cleanup.test.ts
Extend ContextSpec with worktreeList and branchExists; update git.raw mock to return worktree list and branch --list driven by these values; update test to simulate a worktree still listed after removal failure.

Sequence Diagram

sequenceDiagram
  participant WorkspaceCleanup
  participant Git
  participant CloudBackend
  WorkspaceCleanup->>WorkspaceCleanup: canonicalize worktree path
  WorkspaceCleanup->>Git: git worktree remove --force --force (canonical path)
  WorkspaceCleanup->>Git: git worktree list (verify removal)
  alt still registered
    WorkspaceCleanup->>WorkspaceCleanup: throw INTERNAL_SERVER_ERROR
  else removed from registry
    WorkspaceCleanup->>CloudBackend: proceed to delete cloud row
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • superset-sh/superset#4801: Modifies the same workspace-cleanup.ts destroy/cleanup flow with worktree and branch deletion sequencing.
  • superset-sh/superset#4693: Previously adjusted worktree-removal handling and related verification logic on the same code paths.

Poem

🐰 I hop through symlinks, nose to the trace,
Canonical paths lead to the right place,
Git lists the truth, no phantom remain,
Branches checked gently, deletions explain,
Cloud rows fall only when git shows the gate.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: shifting workspace-delete cleanup decision logic from error text parsing to actual git state checks.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering problem statement, solution, tests, and verification against production cases.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mini-wolfsbane

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.

@stage-review

stage-review Bot commented Jun 5, 2026

Copy link
Copy Markdown

Ready to review this PR? Stage has broken it down into 3 individual chapters for you:

Title
1 Add git state verification helpers
2 Update workspace and branch cleanup logic
3 Update cleanup tests with state-based mocks
Open in Stage

Chapters generated by Stage for commit f16641c on Jun 5, 2026 4:44am UTC.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/host-service/test/workspace-cleanup.test.ts (1)

67-70: ⚡ Quick win

Add a regression test for unexpected show-ref failures

Please add a case where git.raw(["show-ref", ...]) fails for reasons other than missing ref and assert cleanup reports a warning instead of branchDeleted: true. This guards the new branch-existence contract and prevents silent false-success regressions.

Also applies to: 83-85

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/host-service/test/workspace-cleanup.test.ts` around lines 67 - 70,
Add a regression test case to workspace-cleanup.test.ts that simulates
git.raw(["show-ref", ...]) failing with an error that is not "ref not found"
(e.g., throw new Error("permission denied")) by changing the branchExists mock
(the async mock around spec.branchExists currently throwing "ref not found") to
throw a non-ref-not-found error for one test case; then assert that the cleanup
result reports a warning (not branchDeleted: true) and that no silent success is
returned. Repeat the same adjustment for the similar mock case at the other
location referenced (the block around lines 83-85) so both paths validate that
unexpected show-ref failures produce a warning instead of treating the branch as
deleted.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts`:
- Around line 399-409: The helper localBranchExists currently swallows all
errors from git.raw and returns false; change it to only return false for the
explicit "ref not found" case and rethrow other errors so they surface as
warnings. Update localBranchExists (the call to
git.raw(["show-ref","--verify","--quiet",`refs/heads/${branch}`])) to catch the
thrown error, inspect the Git error (e.g. check error.exitCode === 1 or the
error.message/stderr that indicates the ref does not exist), return false for
that specific case, and rethrow the error for any other exit codes or messages.

---

Nitpick comments:
In `@packages/host-service/test/workspace-cleanup.test.ts`:
- Around line 67-70: Add a regression test case to workspace-cleanup.test.ts
that simulates git.raw(["show-ref", ...]) failing with an error that is not "ref
not found" (e.g., throw new Error("permission denied")) by changing the
branchExists mock (the async mock around spec.branchExists currently throwing
"ref not found") to throw a non-ref-not-found error for one test case; then
assert that the cleanup result reports a warning (not branchDeleted: true) and
that no silent success is returned. Repeat the same adjustment for the similar
mock case at the other location referenced (the block around lines 83-85) so
both paths validate that unexpected show-ref failures produce a warning instead
of treating the branch as deleted.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c06ac882-1796-4dd9-aa33-cf142feccb80

📥 Commits

Reviewing files that changed from the base of the PR and between a928499 and 0eabd56.

📒 Files selected for processing (2)
  • packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts
  • packages/host-service/test/workspace-cleanup.test.ts

@greptile-apps

greptile-apps Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes two workspace-delete failure modes introduced by #4801: hard failures from error-text–dependent worktree removal, and false "branch not found" warnings when a branch was already gone. It replaces both with authoritative git-state checks — consulting git worktree list --porcelain after the remove attempt, and git show-ref --verify before the branch delete.

  • Worktree removal now calls git worktree remove --force --force against the canonicalized (realpath) path, then reads the git registry via listGitWorktrees/isRegisteredWorktree to decide success or failure; the macOS /var/private/var symlink issue is handled by normalizeWorktreePath on both sides of the comparison.
  • Branch deletion now gates on localBranchExists (show-ref --verify) before attempting -D, so an already-absent ref is treated as success rather than surfacing a warning toast.

Confidence Score: 3/5

The cleanup logic is sound in the common case, but a git error on the post-remove worktree list call is silently treated as not registered, allowing the cloud row to be deleted while the worktree directory remains on disk.

The authoritative-git-state approach is the right direction, but listGitWorktrees unconditionally swallows its own errors and returns an empty slice. Any transient or structural failure of git worktree list makes isRegisteredWorktree return false, causing worktreeRemoved = true and letting the cloud delete proceed with the directory still present. There is no test covering this failure path.

The core logic in workspace-cleanup.ts around isRegisteredWorktree deserves a second look; worktree-list.ts may need a throwing variant or the caller should not tolerate an empty result when the list command itself errors.

Important Files Changed

Filename Overview
packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts Replaces error-string-based worktree/branch cleanup with git-state checks via listGitWorktrees/show-ref; the worktree check delegates to a helper that silently returns [] on any git error, which can falsely indicate not registered and allow cloud delete to proceed with the directory still on disk.
packages/host-service/test/workspace-cleanup.test.ts Test mock updated to model worktree list and show-ref git reads; the key failure scenario (worktree list itself throwing) is not covered, leaving the silent-pass behavior untested.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[destroy called] --> B{worktreePath exists on disk?}
    B -- No --> C[worktreeRemoved = true]
    B -- Yes --> D[open git repo]
    D -- throws --> E{path still exists?}
    E -- Yes --> F[throw INTERNAL_SERVER_ERROR]
    E -- No --> C
    D -- ok --> G[git worktree remove --force --force canonicalPath]
    G --> H[git worktree list --porcelain via listGitWorktrees]
    H -- list fails silently returns [] --> I[isRegisteredWorktree = false]
    H -- list succeeds --> J{path still in registry?}
    J -- Yes --> F
    J -- No --> I
    I --> K[worktreeRemoved = true]
    K --> L[cloud delete]
    C --> L
    L --> M{deleteBranch?}
    M -- No --> N[sqlite cleanup]
    M -- Yes --> O[show-ref --verify refs/heads/branch]
    O -- throws any error --> P[branchDeleted = true, no delete attempted]
    O -- success --> Q[git branch -D]
    Q -- ok --> R[branchDeleted = true]
    Q -- throws --> S[warning added, branchDeleted = false]
    P --> N
    R --> N
    S --> N
    N --> T[return result]
Loading

Comments Outside Diff (1)

  1. packages/host-service/test/workspace-cleanup.test.ts, line 374-413 (link)

    P2 Missing test: worktree list failure during the post-remove check

    The test covers worktreeRemove throwing + worktreeList returning the path still registered (the normal failure path). It does not cover worktreeList itself throwing an exception — the case where listGitWorktrees swallows the error and returns [], causing the code to conclude removal succeeded and proceed to cloud delete with the directory still on disk. Adding a test where worktreeList: () => Promise.reject(new Error("list boom")) would surface the silent-failure described in the related comment.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: packages/host-service/test/workspace-cleanup.test.ts
    Line: 374-413
    
    Comment:
    **Missing test: `worktree list` failure during the post-remove check**
    
    The test covers `worktreeRemove` throwing + `worktreeList` returning the path still registered (the normal failure path). It does not cover `worktreeList` itself throwing an exception — the case where `listGitWorktrees` swallows the error and returns `[]`, causing the code to conclude removal succeeded and proceed to cloud delete with the directory still on disk. Adding a test where `worktreeList: () => Promise.reject(new Error("list boom"))` would surface the silent-failure described in the related comment.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts:322-331
**`listGitWorktrees` failure silently looks like "not registered"**

`listGitWorktrees` (in `worktree-list.ts`) catches all exceptions and returns `[]`. When the `git worktree list --porcelain` call fails for any reason (broken index, lock contention, transient IO error), `isRegisteredWorktree` receives an empty list, returns `false`, and the code falls through to `worktreeRemoved = true` — allowing the cloud delete to proceed even if the directory is still on disk. This is the exact orphan-disk scenario #4801 was designed to prevent.

A `git worktree list` failure should be treated as "state unknown" and block the cloud delete, not silently pass. Consider propagating the error from `listGitWorktrees` in this context (or wrapping the call to throw instead of return `[]`).

### Issue 2 of 3
packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts:399-408
**`localBranchExists` conflates "ref not found" with git infrastructure errors**

`git show-ref --verify` exits with code 1 when the ref is absent, but exits with code 128 (`fatal:`) when the repo is inaccessible or corrupted. Both cause `simple-git` to throw, so both return `false` here. If git is momentarily unavailable, `localBranchExists` returns `false`, `branchDeleted` is set to `true`, and the branch is never actually deleted — producing a misleading return value to the caller.

### Issue 3 of 3
packages/host-service/test/workspace-cleanup.test.ts:374-413
**Missing test: `worktree list` failure during the post-remove check**

The test covers `worktreeRemove` throwing + `worktreeList` returning the path still registered (the normal failure path). It does not cover `worktreeList` itself throwing an exception — the case where `listGitWorktrees` swallows the error and returns `[]`, causing the code to conclude removal succeeded and proceed to cloud delete with the directory still on disk. Adding a test where `worktreeList: () => Promise.reject(new Error("list boom"))` would surface the silent-failure described in the related comment.

Reviews (1): Last reviewed commit: "fix(host-service): decide workspace-dele..." | Re-trigger Greptile

Comment on lines +322 to +331
if (await isRegisteredWorktree(git, local.worktreePath)) {
// git still tracks a live worktree here — removal genuinely
// failed. Keep the cloud row so the workspace stays visible and
// retryable instead of orphaning disk past the cloud commit point.
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: `Failed to remove worktree at ${local.worktreePath}`,
});
}
worktreeRemoved = true;

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 listGitWorktrees failure silently looks like "not registered"

listGitWorktrees (in worktree-list.ts) catches all exceptions and returns []. When the git worktree list --porcelain call fails for any reason (broken index, lock contention, transient IO error), isRegisteredWorktree receives an empty list, returns false, and the code falls through to worktreeRemoved = true — allowing the cloud delete to proceed even if the directory is still on disk. This is the exact orphan-disk scenario #4801 was designed to prevent.

A git worktree list failure should be treated as "state unknown" and block the cloud delete, not silently pass. Consider propagating the error from listGitWorktrees in this context (or wrapping the call to throw instead of return []).

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts
Line: 322-331

Comment:
**`listGitWorktrees` failure silently looks like "not registered"**

`listGitWorktrees` (in `worktree-list.ts`) catches all exceptions and returns `[]`. When the `git worktree list --porcelain` call fails for any reason (broken index, lock contention, transient IO error), `isRegisteredWorktree` receives an empty list, returns `false`, and the code falls through to `worktreeRemoved = true` — allowing the cloud delete to proceed even if the directory is still on disk. This is the exact orphan-disk scenario #4801 was designed to prevent.

A `git worktree list` failure should be treated as "state unknown" and block the cloud delete, not silently pass. Consider propagating the error from `listGitWorktrees` in this context (or wrapping the call to throw instead of return `[]`).

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

Comment on lines +399 to +408
async function localBranchExists(
git: Awaited<ReturnType<HostServiceContext["git"]>>,
branch: string,
): Promise<boolean> {
try {
await git.raw(["show-ref", "--verify", "--quiet", `refs/heads/${branch}`]);
return true;
} catch {
return false;
}

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.

P2 localBranchExists conflates "ref not found" with git infrastructure errors

git show-ref --verify exits with code 1 when the ref is absent, but exits with code 128 (fatal:) when the repo is inaccessible or corrupted. Both cause simple-git to throw, so both return false here. If git is momentarily unavailable, localBranchExists returns false, branchDeleted is set to true, and the branch is never actually deleted — producing a misleading return value to the caller.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts
Line: 399-408

Comment:
**`localBranchExists` conflates "ref not found" with git infrastructure errors**

`git show-ref --verify` exits with code 1 when the ref is absent, but exits with code 128 (`fatal:`) when the repo is inaccessible or corrupted. Both cause `simple-git` to throw, so both return `false` here. If git is momentarily unavailable, `localBranchExists` returns `false`, `branchDeleted` is set to `true`, and the branch is never actually deleted — producing a misleading return value to the caller.

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

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

2 issues found across 2 files

Architecture diagram
sequenceDiagram
    participant Client as API Client
    participant Handler as runDestroy()
    participant Git as Git CLI
    participant RegCheck as isRegisteredWorktree()
    participant BrCheck as localBranchExists()

    Note over Handler,BrCheck: NEW: Cleanup success decided on git state, not error text

    Client->>Handler: destroy workspace

    Note over Handler: Canonicalize worktree path<br/>(resolve /var↔/private/var)
    Handler->>Handler: git worktree remove --force --force <canonicalPath><br/>(best‑effort, ignore error)

    Handler->>RegCheck: is this path still registered?
    RegCheck->>Git: git worktree list --porcelain
    Git-->>RegCheck: output (realpath‑canonicalized entries)
    RegCheck-->>Handler: true / false

    alt still registered → genuine failure
        Handler-->>Client: TRPCError(INTERNAL_SERVER_ERROR)
    else unregistered → success
        Handler->>Handler: worktreeRemoved = true
    end

    Handler->>BrCheck: does branch exist?
    BrCheck->>Git: git show-ref --verify --quiet refs/heads/<branch>
    alt exit code 0 → branch exists
        BrCheck-->>Handler: true
        Handler->>Git: git branch -D <branch>
        alt delete OK
            Git-->>Handler: success
            Handler->>Handler: branchDeleted = true
        else delete fails
            Git-->>Handler: error
            Handler->>Handler: push warning
        end
    else exit code non‑zero → branch absent
        BrCheck-->>Handler: false
        Handler->>Handler: branchDeleted = true (no warning)
    end

    Handler-->>Client: { success, branchDeleted, warnings }
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts Outdated
Comment thread packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts Outdated
@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

…success

Address review feedback: both state checks swallowed all errors and
returned the "happy" value, so a transient git failure read as false
success — the orphan-disk / silent-skip outcome this change is meant to
prevent.

- isRegisteredWorktree now calls `git.raw` directly instead of the
  swallowing `listGitWorktrees`, so a failed `worktree list` throws. The
  worktree step treats that "state unknown" as still-registered and blocks
  the cloud delete rather than proceeding.
- localBranchExists uses `git branch --list` (exit 0 whether or not the
  ref exists), so a thrown error is a real git failure, not a missing
  ref. The branch step's try/catch turns such a failure into a warning
  instead of a false `branchDeleted = true`.

Unit mock models `branch --list` as the existence probe.
@saddlepaddle

Copy link
Copy Markdown
Collaborator Author

Addressed in f16641c. Both helpers were swallowing all git errors and returning the happy value, so a transient git failure read as false success — the exact orphan-disk / silent-skip outcome this PR prevents.

  • isRegisteredWorktree now calls git.raw directly (not the swallowing listGitWorktrees), so a failed worktree list throws; the worktree step treats that "state unknown" as still-registered and blocks the cloud delete.
  • localBranchExists uses git branch --list (exit 0 whether or not the ref exists), so a thrown error is a real git failure rather than a missing ref. The branch step turns it into a warning instead of a false branchDeleted = true. (Note: the suggested err.exitCode === 1 check isn't reliable here — simple-git doesn't expose a numeric exit code — hence the branch --list approach.)

@saddlepaddle saddlepaddle merged commit bee9720 into main Jun 5, 2026
17 checks passed
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