Bug Description
specify workflow add <url> writes the download to tempfile.NamedTemporaryFile(..., delete=False). Cleanup on failure is only in except Exception. Two paths skip that cleanup:
except typer.Exit: raise re-raises without unlinking.
KeyboardInterrupt (a BaseException) is not an Exception, so Ctrl+C during the size-limited read leaves the file in the system temp directory.
The later finally: tmp_path.unlink(...) only runs after a successful download, when install has started.
The comment next to the NamedTemporaryFile already says the path is assigned immediately so a failed read can still remove the file. The typer.Exit and BaseException paths were left out.
Steps to Reproduce
- Run
specify workflow add with an HTTPS URL that starts streaming a large body (or mock read_response_limited to block).
- Press Ctrl+C while bytes are being written to the temp file.
- List the system temp directory for a leftover
*.yml, *.download, or archive-suffixed file created by this process.
Alternatively, read src/specify_cli/workflows/_commands.py around the download try/except typer.Exit/except Exception block and the later install finally.
Expected Behavior
Any exit after the temp file is created (error, typer.Exit, or interrupt) should unlink it, matching the existing best-effort cleanup on other download errors.
Actual Behavior
Interrupt or typer.Exit during download leaves delete=False temp files behind.
Specify CLI Version
0.16.5.dev0 (main at bf88c9f9a82f)
AI Agent
Grok Build
Operating System
macOS 26.6.1
Python Version
Python 3.14.7
Additional Context
Suggested fix: catch BaseException for cleanup in the download try, or put download and install under one try/finally that always unlinks tmp_path. I can open a PR if wanted.
Posted on behalf of @SebTardif by Grok (model: grok-4.6).
Bug Description
specify workflow add <url>writes the download totempfile.NamedTemporaryFile(..., delete=False). Cleanup on failure is only inexcept Exception. Two paths skip that cleanup:except typer.Exit: raisere-raises without unlinking.KeyboardInterrupt(aBaseException) is not anException, so Ctrl+C during the size-limited read leaves the file in the system temp directory.The later
finally: tmp_path.unlink(...)only runs after a successful download, when install has started.The comment next to the
NamedTemporaryFilealready says the path is assigned immediately so a failed read can still remove the file. Thetyper.ExitandBaseExceptionpaths were left out.Steps to Reproduce
specify workflow addwith an HTTPS URL that starts streaming a large body (or mockread_response_limitedto block).*.yml,*.download, or archive-suffixed file created by this process.Alternatively, read
src/specify_cli/workflows/_commands.pyaround the downloadtry/except typer.Exit/except Exceptionblock and the later installfinally.Expected Behavior
Any exit after the temp file is created (error,
typer.Exit, or interrupt) should unlink it, matching the existing best-effort cleanup on other download errors.Actual Behavior
Interrupt or
typer.Exitduring download leavesdelete=Falsetemp files behind.Specify CLI Version
0.16.5.dev0 (main at bf88c9f9a82f)
AI Agent
Grok Build
Operating System
macOS 26.6.1
Python Version
Python 3.14.7
Additional Context
Suggested fix: catch
BaseExceptionfor cleanup in the downloadtry, or put download and install under onetry/finallythat always unlinkstmp_path. I can open a PR if wanted.Posted on behalf of @SebTardif by Grok (model: grok-4.6).