# My AI Agents Leave Messes Behind. Here Is How I Teach Them to Clean Up

Author: Maksym Tytarenko | Date: 2026-11-04 | Category: AI & ML | Tags: ai-agents, claude-code, developer-tooling, automation
Canonical: https://www.tytarenkoagency.com/blog/my-ai-agents-leave-messes-behind-here-is-how-i-teach-them-to-clean-up

> Parallel AI coding agents leave three kinds of mess: colliding working trees, traps from interrupted operations, and ghost processes. Today 18 relays and 9 orphaned sessions ate my laptop's memory. Here are the mechanisms that replace 'be careful'.

I run a lot of AI coding agents on one laptop. On a normal day there are more than a dozen Claude Code sessions open at once: one driving a queue of agent-written pull requests, others working in separate repositories, a few waiting on a review. Today I counted 18 interactive sessions alive on the machine at the same time.

That setup is productive, and it is also messy in a way nobody warns you about. Agents are very good at starting things. They are much worse at finishing the part of the job that a tidy human does without thinking: closing what they opened, putting back what they moved, and leaving the workspace in a state the next person can trust.

![A visual representation of AI coding agents at work on a laptop, illustrating the complexity and messiness of managing multiple sessions.](https://media.tytarenkoagency.com/ai-generated/uploads/20261104_012458_f74fcaf8.png)

This article is about three kinds of mess my agents left behind, what each one cost, and the rule I wrote for each. None of the rules is "be more careful." Every one of them is a mechanism, because an instruction to be careful is a wish, and agents do not keep wishes.

## Mess one: agents sharing a working tree destroy each other's work

The first lesson came the hard way. On August 31 a parallel session checked out the main branch in a shared checkout and hard-reset it to the remote, in the middle of another session's edit. The other session's uncommitted work was gone, silently. Nothing failed loudly. The work simply was not there any more.

It was not the first warning. Earlier in August, in another repository, in-progress files from one session were swept into unrelated commits by another session three times over three days. Each incident looked like a small accident. Together they described the actual problem: several agents sharing one working tree, each switching branches and resetting whatever it needed, is a lost-update machine.

**The rule: every session that writes files works in its own git worktree.** The main checkout stays on the main branch and is read-only by convention. Searching and reading there is fine; the first edit is the trigger to move into a worktree. I did not leave that to convention for long. A hook now denies the first file edit into a repository's main checkout while it sits on main, so an agent that forgets the rule gets stopped at the moment it matters, not reminded in a document it did not read. Agents also commit early inside their worktree, so even a stray reset finds nothing uncommitted to destroy.

### The worktree rule creates its own mess

Worktrees solve the collision problem and immediately create a new one: folders. Every task leaves a directory behind. Clones made for a one-off check pile up next to them. After a few weeks the projects folder is full of directories whose purpose nobody remembers, and deleting the wrong one is exactly the kind of silent data loss the worktree rule exists to prevent.

So cleanup goes through a dedicated sweep tool, never through an agent typing a delete command. A session registers a worktree when it creates it, and marks it finished once the pull request has merged. The sweep removes a folder only when three checks pass:

1. a merged pull request covers the local tip of the branch, so nothing in the folder exists only there;
2. the folder holds no real uncommitted changes;
3. no process is still holding the folder.

A folder that fails any check is not deleted. Folders with no safe automatic answer are queued and shown to me in small groups, so the decision about anything ambiguous stays with a human. Two shortcuts are banned outright: force-removing a worktree, and killing a process to free a folder. Both get the folder deleted, and both skip the checks that make deletion safe.

The principle behind the tool is simple. Cleanup by agents is fine. Cleanup that skips the checks is the original bug wearing a different hat.

## Mess two: an interrupted operation leaves a trap behind

The second class of mess is quieter. Today, while syncing my shared agent configuration repository, I found a stale, empty git lock file left behind by a pull that had been interrupted hours earlier. The interrupted pull had also left one of my hook scripts truncated to zero bytes.

Think about what that means for an agent setup. The hook was a guard. An empty guard file does not fail; it just does nothing. Every session that loaded it would have run without that protection and nobody would have been told.

The repair was mechanical: remove the stale lock, set the half-written leftovers aside in a stash instead of deleting them, and fast-forward to the remote. The hook came back at its full 336 lines. The lesson was not mechanical. Tools that modify files in place can leave traps when they are interrupted, and the trap is often in a file you did not touch. So after any interrupted operation I now check the repository status for changes in files nobody meant to edit, before trusting the state. The same habit applies to local test runs of daemon code, which write runtime state files into whatever directory they ran in: before finishing, look for strays and delete the dead ones.

## Mess three: ghost processes eat the machine

The third mess is the one that made me write this article, because it is the one I could not see.

Today the laptop started killing my agents' background jobs. The operating system reported that it was critically low on memory and stopped long-running watchers my orchestrating session depended on. It happened three times in one working session. Each time, the work that job was doing simply stopped, and someone had to notice.

When I finally looked, the machine had 58.2 of 63.8 GB of commit memory in use, with 4.5 GB of physical memory free. The process table told the story:

- 29 Claude Code processes holding 12.46 GB;
- 18 Unity MCP relay processes holding 9.3 GB;
- 73 Node processes, mostly MCP servers, holding 4.85 GB;
- a browser with 57 processes holding 9.73 GB.

The relay processes were the giveaway. Each agent session starts its own relay to talk to the Unity editor. The Unity editor was not running at all. Eighteen relays were connected to nothing, each holding a few hundred megabytes, some of them for more than a day.

This had happened before. In September I had already found 13 such relays accumulated with no editor open, 833 MB, the oldest three days old. I had written that down as a note for the next time. A note is not a mechanism, and the next time arrived.

### Why the sessions never die

The relays live exactly as long as the session that started them. So the real question was why sessions lived so long.

Every session writes a small status file with its process id, its kind, whether it is idle or busy, and when it last did anything. Reading those files and walking the process tree gave a clear answer with two parts.

First, closing a terminal tab does not end the agent. My sessions run inside a terminal multiplexer whose background daemon had been running for two days. Closing a tab killed the shell, but the agent process underneath it kept running. Nine of the 18 interactive sessions had a dead parent: no window, no way to type into them, still holding memory and still holding their relays and MCP servers.

Second, an agent session has no idle exit. A session in a tab I had simply forgotten about lives forever. Nine sessions had been idle for between 15 and 33 hours. The oldest had been idle for 32.8 hours.

So the leak was not inside any one session. It was two missing endings: an abandoned window does not end its process, and an idle session never ends itself.

### The immediate fix, and its price

The fastest win was the relays, since Unity was not open. I stopped 17 of them and kept the one belonging to my current session. Commit memory dropped from 58.2 GB to 49.6 GB. One command freed 8.6 GB.

The price was explicit before I did it: every other open session lost its Unity connection until restarted. Since the editor was not running, that cost nothing today. It is the kind of tradeoff an agent should state and a human should approve, which is how it went.

### The rule: a reaper that only takes what nobody can use

Killing processes by hand every few weeks is the note-for-next-time pattern again. So I designed a session reaper, and the design questions were more interesting than the code.

**When does it run?** On session start, launched as a detached process so it never delays the new session. Sessions pile up precisely when new ones are opened, so that is the natural moment, and it needs no separate scheduler.

**What may it kill?** This is the decision that matters. The reaper kills a session's whole process tree, the agent plus its relay and MCP servers, only when two conditions hold at once:

1. **the session is an orphan:** its parent process is dead, meaning no window can ever show it again;
2. **the session is idle:** its own status file says idle, and it has not done anything for more than 2 hours.

A session whose window is still open is never touched, no matter how long it has been idle. The two-hour threshold covers the case where a window only looks gone. The threshold can be changed with an environment variable.

**What does killing cost?** Nothing that matters. The full conversation is kept on disk, and a reaped session can be resumed with its history intact. What dies is the memory, not the work.

**What is out of scope?** Background sessions and the desktop app run without a terminal on purpose, so the reaper only considers interactive command-line sessions. A relay whose parent agent is already dead is removed regardless of any threshold, because a relay without a session has no use at all. Orphaned Node processes are left alone, because other programs on the machine start Node too, and a reaper that guesses is a reaper that eventually kills the wrong thing.

**What can go wrong?** Two traps came up during the design, and both are the reaper's equivalent of the sweep tool's checks:

- process ids get reused, so a "parent" that exists may be an unrelated program that inherited the number; a parent created after the child is treated as dead;
- a status file can be stale, left over from a process that no longer exists under that id; if the recorded process start time does not match the live process, the reaper skips it and does not trust the file.

The reaper never kills the session that launched it. Every kill is written to a log with the session name, how long it was idle, and how much memory it freed, and a dry-run mode prints what it would kill without touching anything.

Because this tool kills processes on my machine, it gets the same treatment as any change touching money or authentication: an outside adversarial review before it merges. Its tests run through the real entry point against a simulated process table, with a case for each guard, and each guard has a deliberately broken version that must make its test fail. The most important case is the boring one: a session in an open window survives, at any idle age.

## What the three messes have in common

Looking back, each mess has the same shape. An agent starts something: a worktree, a git operation, a session. The ending of that thing is left to chance: a human remembering to delete a folder, an operation never being interrupted, a tab closing cleanly. When the ending does not happen, the leftover is invisible until it costs something real, whether that is lost work, a disabled guard, or a machine killing its own jobs.

The fix has the same shape too:

- **Give every start a defined ending.** A worktree is registered and finished. A session either has a live window or is eligible for reaping.
- **Make the cleanup check before it deletes.** Merged pull request, no real changes, nothing holding the folder. Orphan and idle, never a live tab.
- **Keep the ambiguous cases for a human.** Folders without a safe answer are asked about. A kill with a side effect is stated before it happens.
- **Turn every note into a mechanism.** The September note about relays did not prevent today's incident. A hook, a sweep tool and a reaper do not depend on anyone remembering.

Agents will keep getting better at doing the work. Teaching them to clean up after it is still on us, and the way to teach a machine is not to tell it once. It is to build the ending into the system.

## FAQ

### Why not just close the agent sessions by hand?

Because the problem sessions are the ones you cannot see. Nine of the 18 sessions on my laptop had no window left after their terminal tab was closed, so there was nothing to close by hand. The only way to find them was to read their status files and walk the process tree.

### Does reaping an idle session lose the conversation?

No. The conversation is stored on disk and the session can be resumed with its full history. Reaping frees the memory the process was holding; it does not delete any work.

### Why require both orphan and idle instead of just idle?

Idle alone would kill sessions in open windows that I simply have not used for a while, and those are often waiting on something on purpose. Requiring a dead parent means the reaper only takes sessions nobody can reach. The idle threshold on top protects against a window that only looks gone.

### Why give each agent its own git worktree?

Because agents sharing one working tree switch branches and reset under each other. On August 31 that silently destroyed a session's uncommitted work. A worktree per session removes the shared state entirely, and a hook stops the first edit into the shared main checkout.

### Why not let the agent delete finished worktrees directly?

Because a direct delete skips the checks that make deletion safe. The sweep tool deletes a folder only when a merged pull request covers its work, it has no real uncommitted changes, and nothing is holding it. Anything else is shown to a human.

## Related reading

- [Why I Rejected an 817-Skill Security Pack and Wrote a 2-File Threat-Model Skill](https://www.tytarenkoagency.com/blog/why-i-rejected-an-817-skill-security-pack-and-wrote-a-2-file-threat-model-skill)
- [Unknown Is Not Clean: Metrics for Code Your Agents Write](https://www.tytarenkoagency.com/blog/unknown-is-not-clean-metrics-for-code-your-agents-write)
- [I Turned a Telegram Group Into a Software Project](https://www.tytarenkoagency.com/blog/i-turned-a-telegram-group-into-a-software-project)

