Back to catalogue
WorkflowWorktreeRemove
Clean up artifacts on worktree removal
Removing a worktree reclaims its disk and containers
Runs cleanup tasks when a worktree is removed: stops any Docker Compose services declared in the worktree and removes node_modules to reclaim disk space. Prevents orphaned containers and disk bloat after isolation: worktree subagent sessions.
What does the Clean up artifacts on worktree removal hook do?
Clean up artifacts on worktree removal is a Claude Code WorktreeRemove hook. It fires automatically at that lifecycle event — outside the model, so it can't be skipped or forgotten. Removing a worktree reclaims its disk and containers.
Because it is a deterministic Node.js script, it executes on every matching event without relying on the model to remember — the guarantee that makes agentic workflows safe to automate.
Use cases
- Stop Docker Compose services started inside an isolated worktree
- Reclaim disk space by removing node_modules after worktree cleanup
- Prevent orphaned containers when parallel subagents each run their own stack
Tags
#worktree#docker#cleanup#node_modules#disk-space
settings.json fragment
{
"hooks": {
"WorktreeRemove": [
{
"hooks": [
{
"command": "node $CLAUDE_PROJECT_DIR/.claude/hooks/worktree-remove-cleanup.mjs",
"type": "command"
}
]
}
]
}
}Script · .claude/hooks/worktree-remove-cleanup.mjs
#!/usr/bin/env node
// @hookstack worktree-remove-cleanup
import { execSync } from "node:child_process";
// @hookstack worktree-remove-cleanup
// Nettoie un worktree supprimé : docker compose down + node_modules (WorktreeRemove)
import { existsSync, readFileSync, rmSync } from "node:fs";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
function defaultExec(cmd) {
execSync(cmd, { timeout: 30_000 });
}
export function run(
input,
{ exec = defaultExec, exists = existsSync, rm = rmSync } = {},
) {
const p = input.worktree_path;
if (!p) return null;
const actions = [];
try {
if (
exists(join(p, "docker-compose.yml")) ||
exists(join(p, "docker-compose.yaml"))
) {
exec(
`docker compose -f ${p}/docker-compose.yml down --remove-orphans 2>&1`,
);
actions.push("docker-down");
}
} catch {
/* ignore */
}
try {
const nm = join(p, "node_modules");
if (exists(nm)) {
rm(nm, { recursive: true, force: true });
actions.push("rm-node-modules");
}
} catch {
/* ignore */
}
return { actions };
}
/* v8 ignore next 4 */
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const input = JSON.parse(readFileSync(0, "utf8"));
run(input);
}