Skip to content

Commit 802c001

Browse files
committed
Fix Windows file locking error when rendering with --output-dir
When --output-dir is used without a project file, Quarto creates a synthetic project context with a temporary .quarto directory to manage the render. This fix ensures file handles are closed before attempting to remove the directory, preventing Windows "os error 32" (file in use by another process). The synthetic project pattern: - Triggered when: quarto render file.qmd --output-dir output/ (no _quarto.yml) - Creates temporary .quarto directory in current directory - Uses full renderProject() path (not singleFileProjectContext()) - forceClean flag in RenderOptions signals cleanup needed - After render: close handles (context.cleanup()) then remove directory Improved comments to explain the synthetic project pattern, the dual purpose of the forceClean flag, and critical ordering requirements to avoid file locking issues on Windows. Fixes #13625
1 parent 866fd8d commit 802c001

File tree

3 files changed

+18
-8
lines changed

3 files changed

+18
-8
lines changed

news/changelog-1.9.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,4 @@ All changes included in 1.9:
5959
- ([#13402](https://github.com/quarto-dev/quarto-cli/issues/13402)): `nfpm` (<https://nfpm.goreleaser.com/>) is now used to create the `.deb` package, and new `.rpm` package. Both Linux packages are also now built for `x86_64` (`amd64`) and `aarch64` (`arm64`) architectures.
6060
- ([#13528](https://github.com/quarto-dev/quarto-cli/pull/13528)): Adds support for table specification using nested lists and the `list-table` class.
6161
- ([#13575](https://github.com/quarto-dev/quarto-cli/pull/13575)): Improve CPU architecture detection/reporting in macOS to allow quarto to run in virtualized environments such as OpenAI's `codex`.
62+
- ([#13625](https://github.com/quarto-dev/quarto-cli/issues/13625)): Fix Windows file locking error (os error 32) when rendering with `--output-dir` flag. Context cleanup now happens before removing the temporary `.quarto` directory, ensuring file handles are properly closed.

src/command/render/project.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -886,13 +886,20 @@ export async function renderProject(
886886
);
887887
}
888888

889-
// in addition to the cleanup above, if forceClean is set, we need to clean up the project scratch dir
890-
// entirely. See options.forceClean in render-shared.ts
891-
// .quarto is really a fiction created because of `--output-dir` being set on non-project
892-
// renders
889+
// Clean up synthetic project created for --output-dir
890+
// When --output-dir is used without a project file, we create a temporary
891+
// project context with a .quarto directory (see render-shared.ts).
892+
// After rendering completes, we must remove this directory to avoid leaving
893+
// debris in non-project directories (#9745).
893894
//
894-
// cf https://github.com/quarto-dev/quarto-cli/issues/9745#issuecomment-2125951545
895+
// Critical ordering for Windows: Close file handles BEFORE removing directory
896+
// to avoid "The process cannot access the file because it is being used by
897+
// another process" (os error 32) (#13625).
895898
if (projectRenderConfig.options.forceClean) {
899+
// 1. Close all file handles (KV database, temp context, etc.)
900+
context.cleanup();
901+
902+
// 2. Remove the temporary .quarto directory
896903
const scratchDir = join(projDir, kQuartoScratch);
897904
if (existsSync(scratchDir)) {
898905
safeRemoveSync(scratchDir, { recursive: true });

src/command/render/render-shared.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,14 @@ export async function render(
4848
// determine target context/files
4949
let context = await projectContext(path, nbContext, options);
5050

51-
// if there is no project parent and an output-dir was passed, then force a project
51+
// Create a synthetic project when --output-dir is used without a project file
52+
// This creates a temporary .quarto directory to manage the render, which must
53+
// be fully cleaned up afterward to avoid leaving debris (see #9745)
5254
if (!context && options.flags?.outputDir) {
53-
// recompute context
5455
context = await projectContextForDirectory(path, nbContext, options);
5556

56-
// force clean as --output-dir implies fully overwrite the target
57+
// forceClean signals this is a synthetic project that needs full cleanup
58+
// including removing the .quarto scratch directory after rendering (#13625)
5759
options.forceClean = options.flags.clean !== false;
5860
}
5961

0 commit comments

Comments
 (0)