Appearance
Cleaning up what is left behind
A machine that runs twenty projects and copies them for every pull request fills up quietly. Containers of a directory somebody deleted, a database volume whose project went to the trash months ago, review copies nobody opened since the pull request merged. None of it is visible from any one project, which is why none of it ever gets removed.
dqx prune is the one view of all of it. It reports by default and removes nothing.
bash
dqx prunetext
Mode standard: unused for 7 days or more counts.
Would remove 7 things and free 1.5 GB:
1.0 GB build cache nothing is building from it
281 MB 1 volume its project is gone
245 MB 1 volume anonymous, and nothing uses it
161 kB 1 volume the IDE writes it again when it needs it
1 last-started entry no directory is there any more
1 copy made as a throwaway
1 copy nothing of its own, and unused for over 7 day(s)
a copy shares its blocks with the original, so what you get back is only what the copy changed
Would stop 1 project:
1 project running, and unused for over 7 day(s)
Keeps:
3 copies uncommitted work
1 copy its stack is running
9 projects used in the last 7 day(s)
Kept only because they were used recently:
5 days ago acme-pr-12
2 days ago northwind, acme-two, +3
in the last day northwind-pr-1250, acme, +1
`dqx prune --days 5 --yes` takes the ones used 5 or more days ago.
`dqx prune --yes` does it. `dqx prune -v` lists each item and why.--yes does it. --orphans, --copies and --idle narrow it to one bucket. -v lists every item on its own line, and also what the orphans keep.
The last list is what only age still keeps: a running stack, or a copy with nothing of its own, that somebody used in the last --days. It says how long ago each was used, and the command under it takes the oldest now instead of waiting. A copy with work in it is never on the list, because age alone does not take it.
How long unused is old enough
Two modes, set once per machine:
| Mode | Unused for |
|---|---|
standard | 7 days, and what a machine gets when nothing sets a mode |
aggressive | 1 day |
bash
dqx config machine pruneMode aggressiveThat writes pruneMode: aggressive to ~/.config/dqx/config.yml. dqx prune --mode standard uses the other mode for one run, and --days N beats both. dqx doctor and the MCP tools follow the machine's mode too. A mode only changes how long unused has to be: work, unpushed commits and a running stack keep a copy in both.
Used is the latest of three moments: the last dqx up, the moment a copy was made, and the last request its web container logged. The last one is what makes a day safe: a stack you started on Monday and clicked through all Tuesday was used on Tuesday. A stopped stack has no container to ask, so it falls back to when it was started.
Which files are keeping a copy
"21 copies, uncommitted work" is a wall you cannot act on. Some of those copies are held by a stray .playwright-mcp log, others by somebody's morning, and the line reads the same either way. -v names the files.
bash
dqx prune --copies -vtext
COPIES 2 to remove
copy ~/projects/northwind-pr-1234 made as a throwaway
copy ~/projects/northwind-x2 nothing of its own, and unused for over 7 day(s)
3 copies kept: uncommitted work
~/projects/northwind-pr-1240
www/app.northwind.test/.playwright-mcp/console-2026-10-16T12-06-32-747Z.log
~/projects/northwind-pr-1250
www/app.northwind.test/resources/views/flux/pagination.blade.php
www/app.northwind.test/.env.example
~/projects/northwind-x3
www/app.northwind.test/app/Models/User.php
www/app.northwind.test/app/Models/Order.php
www/app.northwind.test/composer.lock
www/app.northwind.test/package.json
www/app.northwind.test/tests/Feature/OrderTest.php
… 66 more
1 copy kept: its stack is running
a copy shares its blocks with the original, so what you get back is only what the copy changed
`dqx prune --yes` does it.Five paths per copy and then a count of the rest, so a copy somebody worked in all week cannot bury the twenty under it. Each path is written from the copy's own root, which is what makes the checkout it sits in visible. Only a copy kept by its work has any: one kept by its running stack is kept by the stack, whatever is inside it.
Naming them costs a read of every changed file in every copy, which is why the sweep waits to be asked. Without -v it stops at the first file that is the developer's, because that one file is the whole verdict.
The three buckets
| what it is | what happens to it | |
|---|---|---|
| orphans | leftovers no project owns any more | removed |
| copies | copies that are finished with | stopped, then moved to the trash |
| idle | a stack running that nobody is using | stopped, and nothing else |
Only a directory with a clone: block is ever removed. An original is stopped at most, however old it is, and dqx prune --idle --yes on your main project costs you a dqx up.
What counts as an orphan
A container or a volume whose project directory is gone. An anonymous volume nothing is linked to, which is what a removed stack leaves. A phpstorm_helpers volume with no links, which the IDE writes again the next time it attaches. An untagged image no container uses. Build cache nothing is building from and no image shares, because a shared layer is the image's and stays as long as it does. An entry in dqx's own last-started list pointing at a directory that is not there.
Nothing else. A volume that nothing labels is left alone, because nothing says it is dqx's, and a running container is never touched whatever its directory says.
Why a copy needs more than age
Age proves nothing on its own. A pull request under review for three weeks is exactly the copy somebody would be furious to lose. So work keeps a copy, whatever else is true of it, and only a copy holding nothing of its own is ever taken:
- made with
--temp(an agent environment always is), and its stack is not running, or - unused for longer than the mode allows.
A copy git could not answer for is kept too. The report says which test kept each one, and -v names the files behind the one test that has any.
dqx clone --prune is the command to aim at a throwaway deliberately: it takes every copy made with --temp, work or no work, and lists what it will take before it asks.
What counts as work of its own
Uncommitted changes in a checkout, or commits that are on no remote. The developer's, which is not the same as everything git reports. A copy is a working environment dqx writes into, so a fresh one is dirty from the first second: the generated stack, the copy's own domains rewritten into every .env, the install block dqx keeps in a README. Measured on the fleet this was written for, 27 of 28 copies read as having uncommitted work, and in all but a handful every changed file was one dqx had written itself.
So a changed file is the developer's unless dqx wrote it: a generated file that still carries its generated by dqx line, a file that differs from the last commit only by this copy's own domains, or a block dqx keeps in a file it does not own. It is not theirs either when the copy was made with it, which is true of anything still byte-identical to the same path in the original. Everything else is theirs, a deleted file included, and keeps the copy.
Removal is the same removal as dqx clone --remove: the stack stops with its volumes, the directory goes to the trash, and the original is told to forget the worktrees it held.
Megabytes for the orphans only
A copy is made with cp -Rc, so on APFS it shares its blocks with the original until something changes them. du over the copies on one real machine reported 1 TB on a 460 GB disk. The number is true and the claim is not, so the copies bucket reports a count and no size. What you get back is only what the copy changed, and nothing can measure that in advance.
From the dashboard
The ⋯ menu at the top of the dashboard has prune…. It opens a page with the same report, one box per paragraph, and a switch between the two modes for that page only. It shows the same progress while it checks, and names everything kept only by recent use where the terminal stops at two. every item and why is -v. Prune… asks once more and then does what --yes does. A --read-only dashboard shows the report and keeps the button off.
Each copy under Keeps is a link. It opens a panel with every file that keeps it, and Remove anyway… there does what dqx clone --remove does: the stack stops and the directory goes to the trash, work and all.
From an agent
dqx_prune_check returns the same plan over MCP and is read-only: an agent reports what is lying around, and you are the one who runs dqx prune. dqx doctor --all names it too, once there is a gigabyte or more in it.