# Claude Code Told Me It Was Clear. Nineteen Files Said Otherwise.

> A Claude Code skill that answers "safe to close?" with an audit instead of a memory: worktrees, husks, branches and every pull request behind them.

- Canonical: https://rodrigonask.com/safe-to-close-skill
- Published: 2026-09-10
- Author: Rodrigo Nask (https://rodrigonask.com/)

---

## Key takeaways

- The count of worktrees a session believes it created is exactly the thing that turns out to be wrong. So do not ask the session. Read its transcript.
- Anything written after leaving a worktree lands in the main checkout, on no branch. That is the single most likely thing to be lost forever, and the audit skipped it entirely until it cost me nineteen documents.
- Summary counts are a headline, not a guard. Five husk directories holding sixteen to twenty real files each were once offered up as "pure litter removal with no risk".
- Every item gets an owner: mine, live, abandoned or other. Only mine can block. "Not mine, so I do not care" is how a repository fills up with other people's abandoned work.
- An orphaned worktree with unpushed work is report-only. It may be the only copy in existence, so name it and leave it completely alone.
- The script does the deleting, never the model, and it re-checks every guard itself before it touches anything.

A Claude Code skill is a markdown file that teaches Claude a repeatable procedure. The Safe to Close skill teaches it to answer "safe to close?" with an audit: work out which worktrees, husk directories and branches this session actually created by reading the session's own transcript, check the pull request behind every branch, and return the evidence rather than a feeling.

I run several Claude Code sessions against one repository at the same time. Each one takes its own git worktree and its own branch, because a single shared checkout has one HEAD and sessions switching branches thrash each other constantly. I have watched HEAD jump across three branches in the space of a few seconds.

That pattern works. What it also does is accumulate residue. Worktrees whose session was closed weeks ago. Directories git no longer tracks. Local branches that exist nowhere else in the world because nobody ever pushed them. And `C:\projects` has no backup, so "exists nowhere else" is the literal truth.

So at the end of a session I would ask: safe to close? And Claude would tell me, warmly and confidently, from memory.

## The day the answer was wrong

On 8 September a session finished its work, left its worktree, and then wrote two more files: a handback goal file and a plan update, into the repository's `docs` folder. Perfectly reasonable place for them. Except that leaving the worktree had put it back in the main checkout, on no branch, with nothing tracking those files.

The audit at the time reported `blocking: 0`. Safe to close.

A `git clean` would have taken both. So I added the main checkout as the **first** section of the audit, above everything else, and gave it the power to block. On the very next run it found **nineteen** uncommitted plan and goal documents sitting in that folder from that day's other sessions.

Nineteen files, from one day, that every session involved had already been told were not its problem.

> **Figure (animated on the web version):** A session exits its worktree and keeps writing files. They land in the main checkout on no branch, nineteen of them, untracked. A git clean then sweeps the whole row away.
>
> The audit said **blocking: 0**. On the very next run, with the main checkout finally in scope, it found nineteen uncommitted plan and goal documents sitting there from that day's other sessions.

That is the real failure and it is not a bug in git. It is that "did you clean up?" is a question about memory, and memory is the wrong instrument. The count of worktrees a session believes it created is exactly the thing that turns out to be wrong, because subagents create worktrees the parent never names, and work moves between trees without announcing it.

## So the script does not ask. It reads.

Ownership comes from evidence. The running session's own transcript records every worktree it entered and every branch it created or pushed, so the audit reads that instead of trusting a hand-typed list. It catches worktrees you would never have remembered to name.

Every item then lands in one of four buckets, and the bucket is the whole point:

| owner | means | treatment |
|---|---|---|
| **mine** | proven by this session's transcript | the only thing that can block |
| **live** | another open session is pinned to it right now | never touched, never counted |
| **abandoned** | pinned to a session with no open window, or pinned to nothing | residue. Offer to clear it |
| **other** | ownership unproven, because no session API is configured | reported, never offered |

"Not mine, so I do not care" was the old behaviour, and it is how the repository fills up. A worktree whose session closed three weeks ago is nobody's live work. It is litter.

> **Figure (animated on the web version):** Seven leftover worktrees, husks and branches sorting into four lanes: mine, live, abandoned and other. Only items in the mine lane can block closing the session.
>
> &ldquo;Not mine, so I do not care&rdquo; was the old failure. A worktree whose session closed three weeks ago is nobody's live work. **It is litter, and it is the thing that fills the repo up.**

## What it checks, and why each one is on the list

| checked | why it matters |
|---|---|
| the main checkout | anything written after leaving a worktree lands here, on no branch |
| live worktrees | uncommitted files and unpushed commits die with the directory |
| husk directories | a directory git no longer tracks, and the number one cause of a session that can never resume |
| local branches with no worktree | work that exists nowhere else if it was never pushed |
| the pull request for every branch | number, state, and whether it actually merged |

The default branch comes from `gh repo view`, never from a guess. One of my repositories has `beta` as its default while a stale `origin/HEAD` still says `main`. Guessing produced false "never merged" blockers on every single run until I stopped guessing.

Merged branches on the remote are deliberately out of scope. There are hundreds of them, deleting a branch on a shared remote is an outward-facing act, and they harm nothing sitting there. They get one line and a count.

## Read the column, not the summary

Every row prints its own `clean` flag, and that flag is the only thing that decides whether the delete path will touch it.

> **Figure (animated on the web version):** A summary line reading five husk directories, pure litter, no risk. Below it, each row's own clean column reads NO, and a truncated note expands to reveal it was saying nineteen real entries, inspect first.
>
> Five directories holding sixteen to twenty real files each, offered up as &ldquo;pure litter removal with no risk&rdquo;. The warning was there. **A table column had truncated it to three characters and a full stop.**

This is not a stylistic preference. On the same day as the nineteen files, five husk directories, each holding sixteen to twenty real files, were presented to me as "pure litter removal with no risk". The summary line said residue. The per-item warning said "19 real entries, inspect first" and had been truncated to "19..." by a table column. Only the delete path's own re-check stopped it.

The Note column is never truncated now, and the rule is blunt: **if a row says NO, it may not be described as safe to remove, however harmless the summary made it sound.**

## The three answers

**A. Nothing left, everything merged.** State the evidence in one line and ask nothing.

> Yes, safe to close. Two worktrees this session, both merged (#119, #120), both directories removed, both branches deleted. Nothing on disk.

**B. Everything merged, but something is still on disk.** Say "yes, safe to close" first, because it is true, nothing of yours is at risk. Then offer the leftovers as a separate question, split by owner, with exactly what would be deleted listed before asking.

**C. Anything of yours is blocked.** Do not say safe to close, not even partially. Print every blocking item with its own next action:

```
worktree-foo   dirty (3 files)      -> commit and push before anything else
worktree-bar   pushed, NO PR        -> open a PR
worktree-baz   PR #121 OPEN         -> merge it, or say you are leaving it open
```

If several are blocked, list all of them. The question was for the list, not the first problem.

> **Figure (animated on the web version):** Three possible answers with three exit codes. Zero, all clear. One, safe to close plus leftovers on disk to offer. Two, blocked, with every blocking item listed beside its own next action.
>
> Answer B is two facts, not a hedge: nothing of yours is at risk, and something is still on disk. Answer C lists **every** blocker, because the question was for the list, not for the first problem.

## Two cases that look like rules and are not

**A husk bricking an open session is a repair, not a trespass.** That session cannot resume, ever, while that empty directory exists. Offering to remove it is fixing someone's bug. "Never touch another session's stuff" is about their *work*, and an empty husk is not work. Only ever for a husk with no real files in it.

**An orphaned worktree with unpushed work is report-only.** That directory may be the only copy in existence. Name it, say it has unsaved work, and leave it completely alone. Do not commit it, do not push it to a rescue branch, do not delete it. Somebody gets to decide that case by case, and it is not the model.

## The deleting is the script's job, not the model's

Deletion goes through one path and one path only:

```powershell
& "$env:USERPROFILE\.claude\skills\safe-to-close\audit-worktrees.ps1" `
  -RepoPath "C:\code\your-repo" -Delete -Yes -Items "worktree-foo","husk-bar"
```

It re-runs the entire audit first and re-checks every guard itself, so it does not trust the list it was handed. It refuses any item that is dirty, unpushed, unmerged, owned by an open session, or outside the worktree root. Then it re-audits and prints the result.

Never do this by hand. Not `Remove-Item -Recurse`, not `rm -rf`, not `rmdir /s`. That is precisely the command that once followed a `node_modules` junction back into the main tree and wiped the whole thing. The script's delete walk never descends into a reparse point. A hand-typed one will.

## Run it as a goal

The audit is a good candidate for a Stop hook, because "did you clean up" is exactly the question a model answers too generously. The `/goal` prompt in [the Library lesson](https://www.skool.com/claude-for-marketing/classroom/d97a88f3?md=b0229dde222242858904c095ae8275cc) is ready to paste: it runs the audit, closes every blocker it can close, and does not let the turn end until the audit exits clean or the remaining items are proven to belong to someone else.

If you want the skill that writes prompts like that one, [I published it too](https://rodrigonask.com/build-a-goal-skill).

## Why this matters more when you work alone

On a team, lost work gets noticed by someone else. A colleague pulls the branch and it is not there. Standup surfaces it. The person who wrote it asks where it went.

Working on my own, nobody notices. There is no second pair of eyes on the repository, and the nineteen files would have gone quietly, on a Tuesday, with every session involved having been told it was clear. That is the actual risk of running [a full stack as one person](https://rodrigonask.com/services): not that the work is worse, but that there is nobody downstream to catch what went missing.

So the check has to be a machine, and it has to refuse to be reassuring. Same reason its sibling refuses to call anything done without a screenshot. [The operating model](https://rodrigonask.com/agentic-marketing) only holds if the tools are the ones being sceptical.

The skill, its /goal prompt and a walkthrough video live in the Library inside Claude For Marketing, my community. Membership is $1 a month for every member inside, so today is the cheapest it gets. It needs git, the GitHub CLI, and PowerShell. It reports by default and deletes nothing until you say yes to a list you have already read.

- [Get it in the Library](https://www.skool.com/claude-for-marketing/classroom/d97a88f3?md=b0229dde222242858904c095ae8275cc)
- [Work with me 1-on-1](https://rodrigonask.com/1-on-1-consultancy)

## Frequently asked questions

### What does the Safe to Close skill do?

It audits the repository instead of trusting what the session remembers: the main checkout, every worktree, every husk directory, every local branch, and the pull request behind each one. Then it gives one of three answers with the evidence attached.

### Why not just ask Claude if it finished?

Because subagents create worktrees the parent never names, and work written after leaving a worktree lands somewhere the session is not thinking about. Memory is the wrong instrument for this question.

### What is a husk directory?

A directory under the worktrees folder that git no longer tracks. It is the number one cause of a session that can never resume, because the CLI refuses to resume a session pinned to a path that still exists but is no longer a worktree.

### Does it delete things?

Only through `-Delete -Yes -Items`, only after you approve a list you were shown first, and only after the script re-runs the audit and re-checks every guard itself. Dirty, unpushed, unmerged, live or out-of-root items are refused.

### Do I need to run several Claude sessions for this to be useful?

It is built for that case, but the main-checkout check earns its keep on a single session. Files written after leaving a worktree land there on no branch, and that is the most likely thing to lose permanently.

### What is the optional session API?

One HTTP endpoint that returns which sessions are currently open, so the script can tell live work from abandoned residue. It is unset by default. Without it the script still audits everything and still blocks correctly on your own work, it just reports everything else as other and offers nothing for cleanup.

### Does it need the GitHub CLI?

Yes, for pull request state. A missing `gh` blocks, because unknown is not the same as merged.
