Merge pull request #605: fix(uploads): probe the workspace bounded before a prompt upload

Conflict resolved in test/routes/session-routes.test.ts: #602's
remote-pane-history mock and #605's bounded-path-probe mock are both kept.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-10-13 18:20:21 +02:00
commit 6bbfc44054
5 files changed
+104 -9

No files matched your search

+17 -1
View File
@@ -377,7 +377,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);
@@ -5354,6 +5356,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');