Conversation
…ails resolve_input cleaned up the InputHandler temp directory only for transitive ingest truncation. Any other failure (a zip that fails extraction, a failed clone, a download error after the temp directory exists) was re-raised with the directory still on disk. The graph then fails before temp_dir_for_cleanup reaches the CLI or MCP server, so no caller can remove it: a downloaded zip that fails extraction stays in the temp directory as download.zip, and a failed local zip leaves an empty skillspector_* directory, on every attempt. Call handler.cleanup() before re-raising any resolution error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: kevin9327 <5299031+kevin9327@users.noreply.github.com>
rng1995
left a comment
There was a problem hiding this comment.
[SkillSpector Review]
Reviewed current head cb13d2201217aea8184a7b6554eb06c746c97b88, including the complete diff, surrounding resolver and InputHandler lifecycle, download/archive/clone failure paths, tests, and hosted checks.
Calling handler.cleanup() before re-raising ordinary resolution failures correctly closes the existing leak for the covered ZIP and download cases, and it preserves the original exception. One lifecycle gap still requires correction: the new except Exception path does not run for interruption or cancellation derived from BaseException. A Ctrl-C after the handler allocates its temporary directory can therefore leave _download.partial or download.zip; interruption during Git materialization can additionally leave a live child process and its tree. The inline finding describes the expected cleanup-and-reraise behavior and regression coverage.
The separate Windows read-only deletion limitation belongs to #567 and is not duplicated here. All completed hosted checks pass; test-unit is still running at review time.
| ) | ||
| raise | ||
| except (ValueError, FileNotFoundError): | ||
| except Exception: |
There was a problem hiding this comment.
[P2] Clean up interrupted materializations too
KeyboardInterrupt and cancellation signals derived from BaseException bypass this handler. If interruption arrives after _get_temp_dir() during _download_file, the partial download and handler directory remain; during _clone_git, the child process and its tree can remain as well. Nearby extraction and single-file-copy cleanup already uses except BaseException specifically to release resources before re-raising. Please make failure cleanup cover interruption paths, ensure an interrupted clone terminates its child before tree removal, and add a regression that raises after temp allocation.
Problem
When an input fails to resolve after
InputHandlerhas created its temp directory, the directory is never removed. This happens with a zip that fails extraction, a failed clone, or a download that errors. Each failed attempt leaves anotherskillspector_*directory in the system temp dir.A local zip containing a
../member, scanned through the CLI (Windows 11, Python 3.12.10,mainat4148ab3). The script listsskillspector_*intempfile.gettempdir()before and after:The same archive served from an allowed download host leaves the full download behind. This runs
resolve_inputwith anhttpx.MockTransportserving a 200 KB zip athttps://raw.githubusercontent.com/org/repo/main/skill.zip:Downloads are bounded by
INGEST_MAX_BYTES(100 MiB), and a long-runningskillspector mcpserver accumulates one such directory per failed scan.Cause
resolve_inputcallshandler.cleanup()only in theTransitiveIngestTruncatedErrorbranch. Every other error is re-raised as-is:Once the node raises, the graph never returns
temp_dir_for_cleanup, socleanup_resultin the CLI and MCP server has nothing to remove._extract_zipand_clone_gitdelete their own subdirectory on failure, but the handler's temp directory (and a downloadeddownload.zipin it) belongs to the handler.Fix
Call
handler.cleanup()before re-raising any resolution error. The exception is unchanged. The truncation branch keeps its existing cleanup and logging.InputHandler.cleanup()is already best-effort and does nothing when no temp directory was created.Tests
tests/nodes/test_resolve_input.py::test_failed_materialization_removes_the_handler_temp_diruses the realInputHandlerfor a local zip and for a downloaded zip (served byhttpx.MockTransport).tempfile.mkdtempis redirected intotmp_pathso the test sees exactly the directories the handler created. The test asserts the zip-slip error still propagates and none of those directories remain.Against unmodified
resolve_input.py:With the fix, the whole file passes:
8 passed, 1 skipped(the skip is the existing symlink case on Windows). Branch coverage shows the newexceptpath executed. The CLI reproduction above now reportsleaked temp dirs: [].Suite
pytest -m "not integration and not provider" -p no:randomly, same machine:maintests/unit,tests/opencode,tests/test_*.pytests/nodesThe +2 are the new cases. The failures and errors are identical on both sides and Windows-only (release and
compare_scan_accuracyharnesses, CRLF fixtures, a backslash inside a POSIX file name, and parametrized test IDs over the Windows environment-variable length limit).ruff check src/ tests/: All checks passed.ruff format --check src/ tests/: 246 files already formatted.This is separate from #567, which covers read-only files that survive cleanup on Windows after a successful Git scan. The two touch different files.
🤖 Generated with Claude Code