Skip to content

Empty a repo clone when Windows will not delete its root - #58

Merged
nedonatelli merged 1 commit into
mainfrom
fix/clone-leak-empty-fallback
Sep 7, 2026
Merged

Empty a repo clone when Windows will not delete its root#58
nedonatelli merged 1 commit into
mainfrom
fix/clone-leak-empty-fallback

Conversation

@nedonatelli

Copy link
Copy Markdown
Owner

Third attempt at the SWE-harness clone leak, and the first grounded in a measurement rather than a theory.

What the evidence shows

Instrumenting the failure path over two 2-task runs:

could not remove clone ...swe-repo-django_django-EDY54e: EPERM
  remains: 28 entr(ies): .editorconfig, .eslintignore, .eslintrc, .git, ...
  probe: removing inner '.editorconfig' SUCCEEDED (file level is free)
  probe: rmdir on the clone root FAILED: EBUSY: resource busy or locked

Two facts settle it:

  1. The tree is intact — 28 entries, .git included. fs.rmSync(recursive) aborts on its first operation and cleans nothing, rather than clearing the tree and failing on a final rmdir. I had previously assumed the latter, and that wrong assumption is what made "a directory handle is held" look like a complete explanation.
  2. The root is EBUSY while its contents delete on demand — so the lock is on the directory, not the files.

No child process survives, and the root frees the instant the test process exits, which puts the handle inside the test process.

What this does not do

It does not identify what holds the handle. I stopped looking deliberately. The two previous fixes were root-cause guesses and both changed nothing:

  • bc429f3 — raised the retry budget 1s → 4.5s, on the theory that cleanup raced ShellSession.dispose()'s 3s force-kill
  • 97fe8ab — killed the whole process tree, on the theory that a surviving grandchild held it

A third guess is worth less than working with the behaviour actually measured.

What it does

If the root will not go, take everything inside it. Verified end-to-end: the leftover directory now has 0 entries instead of 28, turning a ~35MB-per-repo-per-run leak into a directory entry that sweepStaleClones() collects on a later run.

For scale: 167 clones and several GB had accumulated over two days of measurement runs before anyone looked at the temp folder.

🤖 Generated with Claude Code

Third attempt at this, and the first one grounded in a measurement rather than a
theory. Two 2-task runs with the failure path instrumented:

  could not remove clone ...swe-repo-django_django-EDY54e: EPERM
    remains: 28 entr(ies): .editorconfig, .eslintignore, .eslintrc, .git, ...
    probe: removing inner '.editorconfig' SUCCEEDED (file level is free)
    probe: rmdir on the clone root FAILED: EBUSY: resource busy or locked

Two facts settle it. The tree is INTACT -- 28 entries, .git included -- so
fs.rmSync(recursive) aborts on its first operation and cleans nothing, rather
than clearing the tree and failing on a final rmdir. And the root is EBUSY while
its contents delete on demand, so the lock is on the DIRECTORY, not the files.
No child process survives, and the root frees the instant the test process
exits, which puts the handle inside this process.

I did not find what holds it, and I stopped looking. The two previous fixes were
root-cause guesses and both did nothing: a longer retry budget (bc429f3) on the
theory that cleanup raced the shell's 3s force-kill, then a whole-process-tree
kill (97fe8ab) on the theory that a grandchild held it. A third guess is worth
less than working with the behaviour actually measured.

So: if the root will not go, take everything inside it. Verified -- the leftover
directory now has 0 entries instead of 28, turning a ~35MB leak per repo per run
into a directory entry that sweepStaleClones() collects later.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

✅ All checks passed

Check Status
Type check ✅ success
Lint ✅ success
Tests ✅ success

Posted by SideCarAI-Bot

@nedonatelli
nedonatelli merged commit d2ba86a into main Sep 7, 2026
4 checks passed
@nedonatelli
nedonatelli deleted the fix/clone-leak-empty-fallback branch September 7, 2026 00:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant