Skip to content

fix(server): use fs.promises.rm to delete temp upload directories#40755

Merged
pavelfeldman merged 1 commit intomicrosoft:mainfrom
SebTardif:fix/delete-temp-dirs
May 9, 2026
Merged

fix(server): use fs.promises.rm to delete temp upload directories#40755
pavelfeldman merged 1 commit intomicrosoft:mainfrom
SebTardif:fix/delete-temp-dirs

Conversation

@SebTardif
Copy link
Copy Markdown
Contributor

@SebTardif SebTardif commented May 9, 2026

Summary

  • _deleteAllTempDirs uses fs.promises.unlink which cannot remove directories (returns EISDIR on Linux, EPERM on Windows)
  • The error is silently caught via .catch(e => {}), so temp upload directories and their contents are never cleaned up
  • Use fs.promises.rm with recursive and force options instead

Failure scenario: Every file upload via setInputFiles creates a temp directory (upload-<guid>) in the artifacts dir. On context close, _deleteAllTempDirs attempts unlink on these directories, which always fails silently. The directories and uploaded files accumulate on disk indefinitely.

Origin: _deleteAllTempDirs was introduced in #12860 (2022-03-18) when temp entries were files. #31165 (2024-06-12) added folder uploads which pushes directories into _tempDirs, but the cleanup method was not updated.

Same pattern in this repo:

  • #40751 -- same fs.promises.rm fix for dashboard recording temp dir (open)

_deleteAllTempDirs uses fs.promises.unlink which cannot remove
directories (returns EISDIR on Linux, EPERM on Windows). The error
is silently caught, so temp upload directories are never cleaned up.
Use fs.promises.rm with recursive and force options instead.

Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>

private async _deleteAllTempDirs(): Promise<void> {
await Promise.all(this._tempDirs.map(async dir => await fs.promises.unlink(dir).catch(e => {})));
await Promise.all(this._tempDirs.map(async dir => await fs.promises.rm(dir, { recursive: true, force: true }).catch(e => {})));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and in other places dealing with tmp, we want it to be a best effort. If a leaking process holding onto one of the files, we should give up and give it a pass.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. The current code uses force: true (ignores ENOENT) and .catch(e => {}) (swallows any remaining errors like EBUSY from held files). So it is already best-effort: it cleans up what it can and silently gives up on the rest.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, there are no retries, I missed that. All good then.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 9, 2026

Test results for "MCP"

7044 passed, 1068 skipped


Merge workflow run.

@pavelfeldman pavelfeldman merged commit dc4d41f into microsoft:main May 9, 2026
43 of 44 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 9, 2026

Test results for "tests 1"

2 flaky ⚠️ [chromium-library] › library/video.spec.ts:719 › screencast › should work with video+trace `@chromium-ubuntu-22.04-node24`
⚠️ [chromium-library] › library/video.spec.ts:682 › screencast › should capture full viewport on hidpi `@chromium-ubuntu-22.04-node20`

41704 passed, 850 skipped


Merge workflow run.

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.

2 participants