fix(uploads): bound the paste-image workspace probe (#516)

The paste-image route wrote under session.workingDir via ensureUploadDir
with no bounded probe, so an upload onto a stalled network mount held one
of libuv's few threadpool workers until the mount answered. Probe the
workspace with probePathKind past the stall cap before the first disk
touch and refuse an unknown mount through describeUnknownPath, the way
POST /api/sessions already does.
This commit is contained in:
JD committed 2026-10-10 15:03:32 -04:00
1 parent aa3730aace
commit 3e75e7509f
5 files changed
+104 -9

No files matched your search

+17 -1
View File
@@ -374,7 +374,9 @@ export function imageMagicMatchesExt(data: Buffer, ext: string): boolean {
* touched, and a file already there is theirs and stays as it is.
*/
async function ensureUploadDir(workingDir: string): Promise<string | null> {
// workingDir is guaranteed to exist (live session).
// workingDir exists (live session) and the caller bounded-probed its mount, so
// the lstat/mkdir below normally start on a reachable path; a mount that dies
// after that probe is the same residual race POST /api/sessions accepts.
const uploadDir = join(workingDir, UPLOADS_DIR);
try {
const dirStat = await fs.lstat(uploadDir);
@@ -5258,6 +5260,20 @@ export function registerSessionRoutes(
);
}
// The upload is written under session.workingDir (ensureUploadDir below), and
// a live session's workspace can sit on a network mount that stopped answering.
// Probe it bounded before the first disk touch, as #516 requires for a path the
// user named, so one dead mount cannot freeze a libuv threadpool worker per
// upload. Only "unknown" (the mount is not answering) is refused here, the way
// the create route does; absent / not-a-directory keep ensureUploadDir's own
// errors, since a live session's workspace should be neither.
if ((await probePathKind(session.workingDir, { pastCap: true })) === 'unknown') {
return createErrorResponse(
ApiErrorCode.OPERATION_FAILED,
describeUnknownPath('Workspace', session.workingDir, { pastCap: true })
);
}
if (!req.isMultipart()) {
reply.code(400);
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Expected multipart/form-data');