Cleaning up a git worktree is supposed to be the boring half of running many coding agents side by side: spin one up per agent, let it work in isolation, tear it down once the work is merged or abandoned. We shipped that teardown with the steps in the wrong order, and the single most ordinary git refusal there is — a worktree with uncommitted or untracked files — could turn a routine delete into a worktree that vanished from the sidebar but never actually left disk, with no terminal left to explain what had gone wrong.
Two Operations Behind One Delete Button
Influxx gives every agent its own git worktree: a full, isolated working copy of the repository, checked out to its own branch, so a Claude Code session rewriting an API layer and a Codex session refactoring the test suite never collide over the same files. That isolation is the entire point of running agents this way. It's also exactly why cleanup matters — once an agent's work is merged, or you decide to abandon the branch, the worktree needs to disappear cleanly. Not linger as clutter in the sidebar, and not sit forgotten as an orphaned directory nobody remembers creating.
If anything, deleting a worktree sounds like it should be the simpler half of the two operations. Creating one has to decide on a branch, a base commit, a filesystem path, and wire up a terminal against all of it. Deleting one just has to undo those decisions. In practice, "delete" is really two separate operations sharing one button:
- Close the terminals: every terminal session attached to that worktree's tab has to be torn down first, since nothing should be reading from or writing to a directory that's about to be removed.
- Remove the worktree: the actual git operation that unregisters the working copy from the repository and clears its files off disk.
Neither step is unusual by itself. git worktree remove is an ordinary, well-documented git command, and closing a terminal session is something Influxx already does constantly as tabs open and close throughout the day. The mistake was never in either step individually. It was in which one we ran first.
The Ordering Bug: Killing the Witness Before Asking the Question
The original deletion flow closed the terminals first and only then attempted the git removal. That ordering feels natural if you think of the terminal as a side effect of the worktree existing, rather than as the one way back into it if something goes wrong. It only becomes a problem once the second step — the actual git worktree remove — fails. And it fails for an extremely common, entirely non-catastrophic reason: git will not remove a worktree that has uncommitted changes or untracked files sitting in it, unless you explicitly force it.
That's not an edge case, it's a Tuesday. An agent leaves a scratch file it never got around to committing. A build step drops generated output nobody staged. A branch gets abandoned halfway through, with a half-written change still sitting in the working copy. All ordinary situations — and all situations where a plain, non-forced worktree removal correctly refuses to proceed, precisely so nobody loses work by accident.
The trouble was that by the time git said no, we'd already said yes to closing the terminal. The worktree stayed exactly where it was, dirty files and all, but the tab was gone, the terminal session was gone, and there was no view left inside Influxx pointing at that directory. Nothing had actually been deleted — the files were still sitting right there on disk — but the tool's own path back to them had been closed before the tool knew it would need to keep that path open.
"The bug was never really about git. It was about the order we chose to destroy things in. We closed the one door that would have let a developer walk back in and finish the job themselves — and we closed it before we even knew whether there was a job left to finish."
— Tobias Lindqvist, Staff Engineer, Platform at ETAPX
From the outside, it read as data loss even though technically nothing had been deleted. The worktree became a directory you could only reach by leaving Influxx entirely — dropping into a system file browser or opening a separate terminal outside the cockpit to go figure out what git thought was going on, and clean it up by hand. For a tool built around the idea that you shouldn't need a separate terminal app or a separate notes app outside one cockpit, a cleanup bug that pushes you back out to a separate terminal is about as self-defeating as it gets.
The Fix: Prove It's Clean Before You Touch Anything
The fix itself is almost anticlimactic: check first, destroy second. Before Influxx closes a single terminal, it now runs a preflight check against the worktree, using the same information a git status --porcelain --untracked-files=all would report. If that check comes back clean, deletion proceeds exactly as it always did — terminals close, then the worktree is removed. If it doesn't come back clean, the deletion stops right there. Nothing gets torn down. The terminal stays open, the worktree stays exactly as it was, and the developer is the one who decides what happens next: commit it, discard it, or just go look.
Why --untracked-files=all, Specifically
The specific flag matters more than it looks like it should. Plain git status, in its default mode, collapses an entire untracked directory into a single summary line instead of listing every file inside it — reasonable for a status message a human is going to skim, wrong for a safety check deciding whether to destroy something. An agent that generated a dozen new files in a new subdirectory, none of them staged, would otherwise show up as one collapsed line instead of a dozen individual warnings. --untracked-files=all forces git to recurse into those directories and report every file individually, so a whole batch of unstaged work can't hide behind a single summarized entry at the exact moment we're deciding whether it's safe to delete something.
"I've got five or six agents going at once on a payments migration, and worktrees pile up faster than I can review them by hand. What I actually want from a delete button is for it to stop me before it does something I can't take back, not apologize after. Now when I try to clean one up and it's still got real changes sitting in it, Influxx just tells me that up front and leaves the tab open. That's the whole feature. It doesn't need to be any smarter than that."
— Mateo Ruiz, senior backend engineer running parallel agent sessions on a payments-platform migration
Orphans Are Not Dirty Worktrees
Here's where the fix could easily have broken something else. Not every worktree that fails to delete cleanly is "dirty" in the git-status sense. Some worktrees are already broken in a completely different way: their own internal git metadata — the reference every worktree keeps, pointing back to the parent repository it belongs to — is missing or invalid. That worktree isn't holding uncommitted work. It's just a leftover directory whose link to the repository that created it has already rotted, typically because something outside Influxx's control got to it first.
Before this fix, Influxx already had a path for cleaning up exactly that kind of orphan automatically, and it had to keep working after the fix shipped. If the new preflight check treated an orphan the same way it treats a dirty worktree — as something that needs a human decision — every orphaned worktree that used to get swept up automatically would suddenly get stuck behind a check that doesn't even apply to it. We'd have traded one stuck-worktree bug for a different one.
"A worktree with real uncommitted changes and a worktree with a dangling reference back to its parent repo can look identical from the sidebar — both refuse to go away cleanly. But one of them has work in it a developer would be furious to lose, and the other is just a stale directory with broken plumbing and nothing left to protect. Treat those the same way and you either destroy real work or leave garbage on disk forever. We needed a reliable way to tell them apart before we could trust the preflight check at all."
— Tobias Lindqvist, Staff Engineer, Platform at ETAPX
So the check for whether a worktree is a genuine orphan now runs before the cleanliness question is even asked. It looks at whether the worktree's own internal reference actually resolves back to the parent repository it claims to belong to. If that link is broken, the worktree is treated as a genuine orphan and goes through the same automatic cleanup path it always has. If the link is intact, the worktree is real, and only then does it get the full clean check described above.
- Genuinely dirty: the worktree's link to its parent repository is intact, but it has uncommitted or untracked files sitting in it. That needs an actual decision from a developer, so the preflight check stops the deletion.
- Genuinely orphaned: the worktree's own reference back to its parent repository is broken or missing entirely. There's no working-tree state left to protect, so the existing automatic cleanup still runs, exactly as it did before this fix.
Getting that distinction right was the actual work here. "Check before you destroy" is one sentence. Making sure that sentence didn't quietly turn every orphaned worktree into a permanently stuck one was most of the fix.
What We Didn't Fix
We're not going to pretend this closes the gap entirely, because it doesn't. There's still a small window between the preflight check running and the actual git worktree remove executing a moment later, and in that window, it's theoretically possible for something to modify the worktree's files — narrowing the unsafe moment down to something much smaller, but not zero.
We looked at closing that window completely with a real file-locking mechanism wrapped around the check-then-delete sequence, and decided against it. That was a deliberate trade-off, not an oversight we're hoping nobody notices. Locking solves a race condition that, in practice, requires something to write into a worktree in the handful of moments between a status check and a removal call — a narrow enough window that we judged the added complexity of real locking wasn't worth it against how rarely it's ever actually hit. The preflight check closes the overwhelmingly common failure mode, where a worktree is dirty and stays dirty until someone looks at it. It doesn't, and right now can't fully, close the far rarer case of something changing the worktree in the exact instant between the check and the delete.
We'd rather say that plainly than market this as fully solved. It isn't. It's substantially safer, which is a different and more honest claim.
Frequently Asked Questions
Does Influxx ever delete a worktree that still has uncommitted changes?
Not through the normal delete path. The preflight check reads the worktree's status the same way git status --porcelain --untracked-files=all would, and if it finds anything — modified tracked files or untracked ones — it stops before closing the terminal or touching the worktree. The tab stays open and the files stay exactly where they were, so you can commit, stash, or discard them and delete again afterward.
What exactly counts as "dirty" for this check?
Anything git status would flag: uncommitted changes to tracked files, and untracked files, checked recursively rather than collapsed into a single "there's a new directory here" summary line. That's intentionally inclusive — a handful of unstaged, machine-generated files count just as much as a manual edit, because both represent work a developer might not want silently discarded.
How does Influxx tell a broken, orphaned worktree apart from one that just has real changes in it?
By checking whether the worktree's own internal reference back to its parent repository is actually valid. A genuinely orphaned worktree — one where that link is missing or broken — has no working-tree state worth protecting, so it still goes through the automatic cleanup path it always has. A worktree with an intact link to its parent gets the full uncommitted-and-untracked-files check before anything is torn down.
Is there any remaining risk of losing work during deletion?
A narrow one, and we're not going to hide it: there's a small window between the preflight check running and the actual git removal a moment later where, in theory, something could still modify the worktree's files. We chose not to add file-locking to close that gap completely, judging it a rare enough race condition that the added complexity wasn't worth it. The fix removes the common failure mode, not every conceivable one.
Does the preflight check slow down deleting a worktree?
Not noticeably. It's a single status read against the worktree before anything else happens — the same kind of operation Influxx already runs constantly to keep source-control state current in the sidebar. The visible difference isn't speed, it's that a delete on a dirty worktree now stops cleanly instead of half-completing.
Why did the original flow close terminals before checking git status in the first place?
Because the terminal felt like a side effect of the worktree existing, not like the one path back into it if something went wrong. Closing it first was simply the more obvious order to write, and it worked fine every time the git removal also succeeded. Nobody had reasoned through what should happen the moment the second step fails — which, for a worktree with any real work sitting in it, is exactly the moment that matters most.
None of this makes worktree deletion an interesting feature, and it shouldn't be one. The goal was always to make "clean up the worktree that finished" as boring as it sounds: check before you destroy, tell the two failure modes apart, and say plainly where the remaining risk sits instead of pretending it's zero. That's a smaller claim than "we solved it," but it's the one that's actually true.

