[BUG] Source directory was deleted

Resolved 💬 3 comments Opened Jan 30, 2026 by Moishe Closed Feb 2, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Claude Code was creating integration tests and running bash commands and then poof the source tree was gone

What Should Happen?

Not that!

Error Messages/Logs

Steps to Reproduce

Here's what I've discovered:

Bug Report: Source directory deleted during Claude Code session

Summary

During a Claude Code session, my entire source directory (~/src/draft-day/) was deleted. Forensic analysis of the session transcript narrows the deletion to a specific Bash tool invocation that used newline-separated commands (which the Bash tool documentation explicitly warns against).

Environment

  • Claude Code version: 2.1.23
  • Model: claude-opus-4-5-20251101
  • Platform: macOS (Darwin 25.2.0)
  • Shell: zsh
  • Session ID: 368e2e1d-bff8-42a2-ad94-de5a2178a965
  • Session slug: soft-orbiting-bird
  • Working directory: /Users/m.lettvin/src/draft-day/backend
  • Git branch: moishe/reader-role

Transcript file

Full transcript is at:

/Users/m.lettvin/.claude/projects/-Users-m-lettvin-src-draft-day/368e2e1d-bff8-42a2-ad94-de5a2178a965.jsonl

What happened

The agent was writing and running integration tests. It ran several make test-integration commands successfully, then attempted to run database setup + test commands manually. During one of these commands, the entire ~/src/draft-day/ directory was deleted.

The command that caused the deletion

At transcript line 1696 (timestamp 2026-01-29T17:03:57.709Z), the agent issued this Bash command:

{
  "name": "Bash",
  "input": {
    "command": "rm -rf /tmp/draftday-integration-test.lock 2>/dev/null\ncd /Users/m.lettvin/src/draft-day\nPGPASSWORD=postgres psql -h localhost -U postgres -c \"DROP DATABASE IF EXISTS draftday_test;\" 2>/dev/null\nPGPASSWORD=postgres psql -h localhost -U postgres -c \"CREATE DATABASE draftday_test;\"\nPGPASSWORD=postgres psql -h localhost -U postgres -d draftday_test -c \"CREATE EXTENSION IF NOT EXISTS vector;\"\ncd backend && goose -dir internal/database/migrations postgres \"postgres://postgres:postgres@localhost:5432/draftday_test?sslmode=disable\" up 2>&1 | tail -1",
    "timeout": 60000
  }
}

Rendered with newlines, the command is:

rm -rf /tmp/draftday-integration-test.lock 2>/dev/null
cd /Users/m.lettvin/src/draft-day
PGPASSWORD=postgres psql -h localhost -U postgres -c "DROP DATABASE IF EXISTS draftday_test;" 2>/dev/null
PGPASSWORD=postgres psql -h localhost -U postgres -c "CREATE DATABASE draftday_test;"
PGPASSWORD=postgres psql -h localhost -U postgres -d draftday_test -c "CREATE EXTENSION IF NOT EXISTS vector;"
cd backend && goose -dir internal/database/migrations postgres "postgres://postgres:postgres@localhost:5432/draftday_test?sslmode=disable" up 2>&1 | tail -1

Key observation: This command uses newline-separated commands, which violates the Bash tool's own documentation:

"DO NOT use newlines to separate commands (newlines are ok in quoted strings)"

None of the individual commands should delete a source directory. The only rm -rf targets /tmp/draftday-integration-test.lock.

Forensic evidence of the deletion timing

The transcript records gitBranch in every event. This field goes from the valid branch name to empty when the .git directory ceases to exist:

| Transcript line | Timestamp | gitBranch | Event |
|----------------|-----------|-----------|-------|
| 1696 | 17:03:57 | moishe/reader-role | Command issued |
| 1697 | 17:06:23 | moishe/reader-role | Progress: 2s elapsed |
| 1710 | 17:06:36 | moishe/reader-role | Progress: 15s elapsed |
| 1711 | 17:06:37 | "" | Progress: 16s elapsed -- directory is GONE |
| 1712 | 17:06:37 | "" | Result: "goose run: internal/database/migrations directory does not exist" |

The directory was deleted between second 15 and second 16 of the command's progress tracking.

Suspicious timing gap

There is a 2 minute 26 second gap between the command being issued (17:03:57) and the first progress event (17:06:23, reporting "2s elapsed"). This means something happened during that window that the progress tracking doesn't account for. This may be when the deletion was initiated, with the gitBranch change only becoming detectable once the .git directory was fully removed (consistent with a large directory taking time to delete).

Prior commands that also violated the newline rule

Two earlier commands also used problematic formatting and failed immediately:

Line 1693 (just before the destructive command):

{"command": "\\\ncd /Users/m.lettvin/src/draft-day && \\\nrm -rf /tmp/draftday-integration-test.lock 2>/dev/null && \\\n..."}

Result: Exit code 126 / (eval):1: permission denied: -- command never executed.

Line 1627 (earlier attempt):

{"command": "\\\n  LOCKDIR=/tmp/draftday-integration-test.lock.test && \\\n  rm -rf \"$LOCKDIR\" && mkdir \"$LOCKDIR\" && \\\n..."}

Result: Exit code 126 / (eval):1: permission denied: -- command never executed.

Both of these started with \ + newline and failed immediately with exit code 126 in zsh. The command at line 1696 used plain newlines (no leading backslash) and did execute -- but with catastrophic side effects.

What was ruled out

  1. Repository code: Grepped for os.Remove, os.RemoveAll, filepath.Walk -- only found os.Remove (single file, not recursive) in importer packages cleaning up temp files. None would be triggered by the handler integration tests.
  1. The make test-integration Makefile target: Contains rm -rf "$$LOCKDIR" but $$LOCKDIR is set to /tmp/draftday-integration-test.lock at the start of the recipe. This ran successfully multiple times earlier without issue.
  1. Other rm commands in the session: All other rm commands either (a) explicitly targeted /tmp/draftday-integration-test.lock, (b) were inside make test-integration which ran successfully, or (c) failed with exit code 126 before executing.
  1. Background processes: No other processes were running on the system.

Complete list of rm-containing commands in the session

| Line | Command (abbreviated) | Result |
|------|----------------------|--------|
| 1627 | \ + rm -rf "$LOCKDIR" (LOCKDIR=/tmp/...lock.test) | Exit 126 -- never executed |
| 1630 | rm -rf /tmp/draftday-integration-test.lock; make test-integration | Success |
| 1646 | rm -rf /tmp/draftday-integration-test.lock; make test-integration | Success |
| 1693 | \ + rm -rf /tmp/draftday-integration-test.lock | Exit 126 -- never executed |
| 1696 | rm -rf /tmp/draftday-integration-test.lock (newline-separated) | Directory deleted during execution |
| 1714 | rm -rf /tmp/draftday-integration-test.lock (newline-separated, post-deletion) | Exit 1 |

Hypothesis

The Bash tool has a known issue with newline-separated commands (hence the documentation warning). When the command at line 1696 was processed, the newlines in the command string were mishandled during shell evaluation, causing the rm -rf to operate on an unintended target. The exact mechanism is unclear, but the circumstantial evidence is strong:

  1. The only rm -rf in the command targets /tmp/draftday-integration-test.lock
  2. The command uses newlines (documented as "do not do this")
  3. The directory deletion is precisely correlated with this command's execution
  4. The 2.5-minute gap before progress tracking suggests unusual processing during command setup

Impact

The entire source directory (~/src/draft-day/) including all uncommitted work was deleted. Recovery was possible from the latest git commit on the remote, but any uncommitted changes were lost.

Claude Model

Opus

Is this a regression?

Yes, this worked in a previous version

Last Working Version

_No response_

Claude Code Version

2.1.23

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

iTerm2

Additional Information

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗