Reported by @mine-cetinkaya-rundel.
When using Positron and clicking the trash/bin icon on the terminal pane running quarto preview, the process is killed without clean shutdown. The transient .quarto_ipynb file created during render is not cleaned up. On the next preview, a new .quarto_ipynb_1 is created, and this accumulates (_2, _3, ...) each time the user repeats the pattern.
This is distinct from #14281, which fixed accumulation within a single preview session (re-renders). This issue is about accumulation across sessions when the process exits ungracefully.
Steps to reproduce
Create test.qmd with Jupyter engine content:
---
format: html
---
```{python}
1 + 1
```
- Open in Positron
- Click the Preview button (opens terminal running
quarto preview test.qmd)
- Wait for render to complete
- Click the bin/trash icon on the terminal pane (kills terminal)
- Click Preview again
- Repeat steps 4-5 a few times
Actual behavior
Files accumulate:
test.quarto_ipynb
test.quarto_ipynb_1
test.quarto_ipynb_2
Expected behavior
No .quarto_ipynb files left behind between preview sessions.
Analysis
In quarto render, the .quarto_ipynb is cleaned up promptly after render completes — cmd.ts calls context.cleanup() which triggers cleanupFileInformationCache() → invalidateForFile() → safeRemoveSync().
In quarto preview, cleanup is deferred: the file sits on disk between renders and is only deleted at process exit (via onCleanup) or before the next re-render (via invalidateForFile). If the process is killed ungracefully (SIGKILL, taskkill, terminal destruction), the exit cleanup never fires.
Historically, cleanupNotebook() in jupyter.ts used to delete the file immediately after execution when keep-ipynb was not set. This was changed in #12793 to integrate with the fileInformationCache system (fixing #12780), but the immediate deletion for the keep-ipynb: false case was dropped as a side effect. The file deletion was fully delegated to the cache cleanup, which only runs at context disposal time.
Suggested fix
Restore immediate deletion in cleanupNotebook() for the keep-ipynb: false case, while preserving the cache integration for keep-ipynb: true:
This restores the original behavior from before #12793 and aligns preview with how quarto render handles cleanup. The execute() function already has a guard that recreates the file if missing (with a comment: "could have been removed by the cleanup step of another render"), so re-renders continue to work.
Related: #14281, #12780, #12793, #11597
Reported by @mine-cetinkaya-rundel.
When using Positron and clicking the trash/bin icon on the terminal pane running
quarto preview, the process is killed without clean shutdown. The transient.quarto_ipynbfile created during render is not cleaned up. On the next preview, a new.quarto_ipynb_1is created, and this accumulates (_2,_3, ...) each time the user repeats the pattern.This is distinct from #14281, which fixed accumulation within a single preview session (re-renders). This issue is about accumulation across sessions when the process exits ungracefully.
Steps to reproduce
Create
test.qmdwith Jupyter engine content:quarto preview test.qmd)Actual behavior
Files accumulate:
Expected behavior
No
.quarto_ipynbfiles left behind between preview sessions.Analysis
In
quarto render, the.quarto_ipynbis cleaned up promptly after render completes —cmd.tscallscontext.cleanup()which triggerscleanupFileInformationCache()→invalidateForFile()→safeRemoveSync().In
quarto preview, cleanup is deferred: the file sits on disk between renders and is only deleted at process exit (viaonCleanup) or before the next re-render (viainvalidateForFile). If the process is killed ungracefully (SIGKILL,taskkill, terminal destruction), the exit cleanup never fires.Historically,
cleanupNotebook()injupyter.tsused to delete the file immediately after execution whenkeep-ipynbwas not set. This was changed in #12793 to integrate with thefileInformationCachesystem (fixing #12780), but the immediate deletion for thekeep-ipynb: falsecase was dropped as a side effect. The file deletion was fully delegated to the cache cleanup, which only runs at context disposal time.Suggested fix
Restore immediate deletion in
cleanupNotebook()for thekeep-ipynb: falsecase, while preserving the cache integration forkeep-ipynb: true:transient && keep-ipynb: mark cache entry as non-transient (existing behavior from Handle keep-ipynb correctly now that fileInformationCache is responsible for the cleaning #12793)transient && !keep-ipynb: delete the file from disk immediately after executeThis restores the original behavior from before #12793 and aligns preview with how
quarto renderhandles cleanup. Theexecute()function already has a guard that recreates the file if missing (with a comment: "could have been removed by the cleanup step of another render"), so re-renders continue to work.Related: #14281, #12780, #12793, #11597