{"record":{"id":"5e79c00266228782","repo":"appwrite/appwrite","slug":"storage-file-already-exists-5e79c0","errorCode":"STORAGE_FILE_ALREADY_EXISTS","errorMessage":"Storage file already exists","messagePattern":"Storage file already exists","errorType":"exception","errorClass":"Exception","httpStatus":409,"severity":"error","filePath":"src/Appwrite/Platform/Modules/Storage/Http/Buckets/Files/Create.php","lineNumber":284,"sourceCode":"                \\ksort($parts);\n\n                $merged['parts'] = $parts;\n                $merged['chunks'] = \\count($parts);\n            }\n\n            return $merged;\n        };\n\n        $prepareUpload = function () use ($authorization, $bucket, &$chunks, $contentRange, $dbForProject, $deviceForFiles, $fileId, $fileName, $fileSize, &$metadata, $folder, $path, $permissions, $response, &$completed): void {\n            $file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));\n            if (!$file->isEmpty()) {\n                $chunks = $file->getAttribute('chunksTotal', 1);\n                $uploaded = $file->getAttribute('chunksUploaded', 0);\n                $metadata = $file->getAttribute('metadata', []);\n\n                if ($uploaded === $chunks) {\n                    if (empty($contentRange)) {\n                        throw new Exception(Exception::STORAGE_FILE_ALREADY_EXISTS);\n                    }\n\n                    $response\n                        ->setStatusCode(Response::STATUS_CODE_OK)\n                        ->dynamic($file, Response::MODEL_FILE);\n\n                    $completed = true;\n\n                    return;\n                }\n            }\n\n            if ($file->isEmpty()) {\n                $deviceForFiles->prepare($path, $metadata['content_type'] ?? '', $chunks, $metadata);\n\n                if (!empty($contentRange)) {\n                    $doc = new Document([\n                        '$id' => ID::custom($fileId),","sourceCodeStart":266,"sourceCodeEnd":302,"githubUrl":"https://github.com/appwrite/appwrite/blob/ce3a85157f30ff289c7511f62ff076005fa106d1/src/Appwrite/Platform/Modules/Storage/Http/Buckets/Files/Create.php#L266-L302","documentation":"In the chunked/resumable file-upload flow, this is thrown when the client reports all chunks as uploaded (chunksUploaded equals chunksTotal) but the request carries no Content-Range header, so the server cannot finalize the upload. The endpoint interprets this as 'the file is already fully uploaded' and refuses the duplicate completion attempt with 409.","triggerScenarios":"POST/PUT /v1/storage/buckets/:bucketId/files (or the chunked continuation) for a file whose uploaded chunk count already equals chunksTotal, sent without a contentRange header — i.e. attempting to re-upload or finalize an already-complete chunked upload.","commonSituations":"Retry frameworks re-sending the initial chunk request after all chunks were already uploaded; two upload workers racing on the same fileId; client SDK resuming an upload the server already finalized; custom upload loops that skip Content-Range on the final chunk.","solutions":["Treat 409 file_already_exists as success and GET /v1/storage/buckets/:bucketId/files/:fileId to confirm the completed file.","Always send the Content-Range header on every chunk, including the final one.","Use a fresh unique fileId if you actually intend a new upload.","Serialize chunked uploads so only one worker drives the same fileId."],"exampleFix":"// before: blind retry re-sends initial chunk without Content-Range\nawait storage.createFile({ bucketId, fileId, file });\n\n// after: catch already-complete uploads\ntry {\n  await storage.createFile({ bucketId, fileId, file });\n} catch (e) {\n  if (e.code === 409) return storage.getFile({ bucketId, fileId }); // already uploaded\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"const f = await storage.getFile({ bucketId, fileId }).catch(() => null);\nif (f && f.chunksUploaded === f.chunksTotal) {\n  return f; // upload already complete; do not re-send chunks\n}","typeGuard":null,"tryCatchPattern":"try {\n  await uploadChunks(...);\n} catch (e) {\n  if (e.code === 409 && e.type === 'file_already_exists') {\n    return storage.getFile({ bucketId, fileId }); // already fully uploaded\n  }\n  throw e;\n}","preventionTips":["Send Content-Range on every chunk request, including the last.","Before resuming, check chunksUploaded vs chunksTotal via getFile.","Use unique fileIds per upload session; never share a fileId across workers.","Mark upload sessions complete locally before the final chunk returns so retries skip it."],"tags":["appwrite","storage","files","chunked-upload","http-409"],"backgroundTag":"file-already-exists","analyzedSha":"ce3a85157f30ff289c7511f62ff076005fa106d1","analyzedAt":"2026-09-08T08:30:15.067Z","contentChangedAt":"2026-09-08T08:30:15.067Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}