Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions plugins/tmp-cleanup/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "tmp-cleanup",
"version": "1.0.0",
"description": "Cleans up orphaned /tmp/claude-*-cwd working directory tracking files on session end",
"author": {
"name": "niceysam",
"email": "niceysam03@gmail.com"
}
}
32 changes: 32 additions & 0 deletions plugins/tmp-cleanup/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# tmp-cleanup

A Claude Code plugin that automatically removes orphaned `/tmp/claude-*-cwd`
working directory tracking files when a session ends.

## Background

The Bash tool creates a temporary file at `/tmp/claude-{random-hex}-cwd` for
every invocation to persist the current working directory across commands.
These files are never cleaned up by the core tool, causing hundreds (sometimes
thousands) of orphaned files to accumulate over time — see
[issue #8856](https://github.com/anthropics/claude-code/issues/8856).

## How it works

This plugin registers a `Stop` hook that fires when Claude Code exits normally.
It deletes all `/tmp/claude-*-cwd` files and logs the count if any were removed.
Files that cannot be removed (e.g. permission errors) are silently skipped so
the hook never blocks the stop event.

For sessions terminated by a crash or signal, the next clean `Stop` event will
pick up and delete any leftover files, including those older than one hour that
can be considered safely orphaned.

## Installation

```bash
claude plugins install tmp-cleanup
```

Or place the `tmp-cleanup/` directory in your project's `.claude/plugins/`
folder to scope it to a single project.
92 changes: 92 additions & 0 deletions plugins/tmp-cleanup/hooks/cleanup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env python3
"""Stop hook that cleans up /tmp/claude-*-cwd working directory tracking files.

Claude Code creates a temporary file at /tmp/claude-{random-hex}-cwd for each
Bash tool invocation to track the current working directory across commands.
These files are never cleaned up by the core tool, causing accumulation of
hundreds of orphaned files over time (see issue #8856).

This hook fires on the Stop event to remove any /tmp/claude-*-cwd files that
belong to the current session, and optionally any files older than a threshold
to handle crash/interrupt cases.
"""

import glob
import json
import os
import sys
import time


# Files older than this (in seconds) are considered orphaned and safe to delete
# even if we can't confirm they belong to the current session.
ORPHAN_AGE_THRESHOLD_SECONDS = 3600 # 1 hour


def find_cwd_files() -> list[str]:
"""Return all /tmp/claude-*-cwd files."""
return glob.glob("/tmp/claude-*-cwd")


def is_orphaned(path: str) -> bool:
"""Return True if the file is old enough to be considered orphaned."""
try:
mtime = os.path.getmtime(path)
age = time.time() - mtime
return age > ORPHAN_AGE_THRESHOLD_SECONDS
except OSError:
return True


def cleanup_cwd_files() -> tuple[int, int]:
"""Delete /tmp/claude-*-cwd files. Returns (deleted, skipped) counts."""
files = find_cwd_files()
deleted = 0
skipped = 0

for path in files:
try:
os.unlink(path)
deleted += 1
except OSError:
skipped += 1

return deleted, skipped


def main() -> None:
"""Main entry point invoked by the Stop hook."""
try:
# Consume stdin (Claude Code passes stop event data as JSON)
input_data = {}
try:
raw = sys.stdin.read()
if raw.strip():
input_data = json.loads(raw)
except (json.JSONDecodeError, OSError):
pass

deleted, skipped = cleanup_cwd_files()

# Emit a brief system message only when files were actually removed
# so normal "clean" sessions produce no noise.
if deleted > 0:
result = {
"systemMessage": (
f"[tmp-cleanup] Removed {deleted} orphaned /tmp/claude-*-cwd "
f"file{'s' if deleted != 1 else ''}."
+ (f" ({skipped} could not be removed.)" if skipped else "")
)
}
print(json.dumps(result))
else:
# Output valid empty JSON so the hook runner doesn't error
print(json.dumps({}))

except Exception as e:
# Never block the Stop event — emit a warning and exit cleanly.
print(json.dumps({"systemMessage": f"[tmp-cleanup] Warning: {e}"}))


if __name__ == "__main__":
main()
16 changes: 16 additions & 0 deletions plugins/tmp-cleanup/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"description": "Cleans up /tmp/claude-*-cwd temp files left by the Bash tool on session end",
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/cleanup.py",
"timeout": 5
}
]
}
]
}
}