Skip to content

Clean up partial jj/git workspace on failed maudebox new - #1

Open
eljaska wants to merge 1 commit into
martint:mainfrom
eljaska:eljaska/cleanup-failed-new
Open

Clean up partial jj/git workspace on failed maudebox new#1
eljaska wants to merge 1 commit into
martint:mainfrom
eljaska:eljaska/cleanup-failed-new

Conversation

@eljaska

@eljaska eljaska commented May 19, 2026

Copy link
Copy Markdown

Summary

jj workspace add --name N -r REV TARGET registers the workspace and creates the target directory before resolving REV. A bad revision (e.g. --from main in a repo whose default branch is master) therefore leaves both behind, and the next maudebox new <same-name> attempt then trips over a hostile second error ("Workspace named 'N' already exists" / "Target path already exists") that obscures the real one and forces manual recovery with jj workspace forget + rm -rf.

This change wraps the workspace/worktree creation step in cmd::new::run so a failure triggers a silent rollback in the source repo:

  • jj path: jj workspace forget <name> + rm -rf <target>
  • git path: git worktree remove --force <target> + rm -rf <target> (mostly defensive — git worktree add is more atomic)

A one-line "Cleaned up partial workspace/worktree at: …" notice is printed only when at least one of the two operations actually removed state, so a pre-creation failure (e.g. stale working copy) doesn't falsely claim cleanup.

Test plan

No CI in this repo and no automated tests added for the cleanup helpers. Verified locally with my repo ~/Developer/jjuicy:

  • Late failuremaudebox new test-late --from nonexistent ~/Developer/jjuicy true. jj workspace add registered the workspace and created the directory, then failed on -r nonexistent. After cleanup: target directory gone, jj workspace list no longer shows test-late. Retry with a valid --from succeeds on the first try.
  • Early failure — same command against a repo with a stale default working copy. Cleanup ran but printed nothing (nothing was actually rolled back).
  • Build & installcargo build --release + cargo install --path . clean.
  • Smokemaudebox new <name> --from <valid-rev> <source> <cmd> still works on the happy path.

🤖 This PR was created by Claude and approved by a human 🧍‍♂️

`jj workspace add --name N -r REV TARGET` registers the workspace and
creates the target directory before resolving REV. A bad revision (e.g.
the user typing `--from main` in a repo whose default branch is `master`)
leaves both behind, and the next attempt with the same name then fails on
"Workspace named 'N' already exists" or "Target path already exists" —
a hostile second error that obscures the real one and forces the user to
manually run `jj workspace forget` plus `rm -rf` to recover.

Wrap the workspace/worktree creation step so a failure triggers a silent
rollback: `jj workspace forget` (or `git worktree remove --force`) in the
source repo, plus `rm -rf` on the target path. Print a one-line notice
only when something was actually rolled back, so a pre-creation failure
(e.g. stale working copy) doesn't claim cleanup it didn't do.
@eljaska
eljaska marked this pull request as ready for review May 19, 2026 18:51

@martint martint left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

The rollback goal is valid, but the cleanup must distinguish state created by this invocation from state that existed beforehand. The inline issues can unregister an existing workspace, misreport partial cleanup, or delete a directory Maudebox does not own. Please also rebase onto current main (the PR currently conflicts in src/cmd/new.rs) and add regression coverage for pre-existing workspace names, early failures, late revision failures, and partial cleanup.

Comment thread src/cmd/new.rs

fn cleanup_failed_jj(source: &Path, name: &str, target: &Path) {
let forgot = Command::new("jj")
.args(["workspace", "forget", name])

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This forget is unconditional after every workspace add error. If target is absent but name already belongs to another workspace, the add fails with Workspace named ... already exists, and this unregisters that pre-existing workspace. I reproduced that with jj 0.44. Please only forget the name when this invocation can prove it registered that name at this target.

Comment thread src/cmd/new.rs
.map(|s| s.success())
.unwrap_or(false);
let removed = std::fs::remove_dir_all(target).is_ok();
if forgot || removed {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

jj workspace forget missing-name exits 0 on jj 0.44 with Nothing changed, so forgot is true and an early failure still prints Cleaned up. The forgot || removed condition also claims full cleanup when one half failed and retry-blocking state remains. Track actual ownership/change and report each residual cleanup failure instead.

Comment thread src/cmd/new.rs
.status()
.map(|s| s.success())
.unwrap_or(false);
let removed_dir = std::fs::remove_dir_all(target).is_ok();

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This recursive deletion (and the identical jj path) does not prove the directory was created by this invocation. Another process can create target after the initial exists() check; if the add then fails for an unrelated reason, this removes unowned contents. Only delete a path whose creation by this invocation has been established.

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