{"record":{"id":"147ef061abb45ed9","repo":"nextcloud/server","slug":"incompatible-node-types","errorCode":null,"errorMessage":"Incompatible node types","messagePattern":"Incompatible node types","errorType":"exception","errorClass":"Sabre\\DAV\\Exception\\BadRequest","httpStatus":400,"severity":"error","filePath":"apps/dav/lib/Connector/Sabre/Directory.php","lineNumber":405,"sourceCode":"\t * @param string $targetName New local file/collection name.\n\t * @param string $fullSourcePath Full path to source node\n\t * @param INode $sourceNode Source node itself\n\t * @return bool\n\t * @throws BadRequest\n\t * @throws ServiceUnavailable\n\t * @throws Forbidden\n\t * @throws FileLocked\n\t * @throws \\Sabre\\DAV\\Exception\\Forbidden\n\t */\n\t#[\\Override]\n\tpublic function moveInto($targetName, $fullSourcePath, INode $sourceNode) {\n\t\tif (!$sourceNode instanceof Node) {\n\t\t\t// it's a file of another kind, like FutureFile\n\t\t\tif ($sourceNode instanceof IFile) {\n\t\t\t\t// fallback to default copy+delete handling\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tthrow new BadRequest('Incompatible node types');\n\t\t}\n\n\t\t$destinationPath = $this->getPath() . '/' . $targetName;\n\n\t\t$targetNodeExists = $this->childExists($targetName);\n\n\t\t// at getNodeForPath we also check the path for isForbiddenFileOrDir\n\t\t// with that we have covered both source and destination\n\t\tif ($sourceNode instanceof Directory && $targetNodeExists) {\n\t\t\tthrow new \\Sabre\\DAV\\Exception\\Forbidden('Could not copy directory ' . $sourceNode->getName() . ', target exists');\n\t\t}\n\n\t\t[$sourceDir,] = \\Sabre\\Uri\\split($sourceNode->getPath());\n\t\t$destinationDir = $this->getPath();\n\n\t\t$sourcePath = $sourceNode->getPath();\n\n\t\t$isMovableMount = false;","sourceCodeStart":387,"sourceCodeEnd":423,"githubUrl":"https://github.com/nextcloud/server/blob/ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3/apps/dav/lib/Connector/Sabre/Directory.php#L387-L423","documentation":"Directory::moveInto() requires the moved node to be a Nextcloud connector Node; a source that is a plain IFile (e.g. FutureFile) falls back to copy+delete, but any other node implementation — typically supplied by a third-party DAV app — produces BadRequest('Incompatible node types'), HTTP 400.","triggerScenarios":"MOVE or COPY of a node provided by a custom DAV app that does not extend the connector Node class (and does not expose IFile) into a Nextcloud files directory.","commonSituations":"Third-party mount/virtual-filesystem apps after DAV connector refactors; nodes registered by abandoned apps that never adopted the connector Node contract; version skew between an app and the server.","solutions":["Update the app providing the node so it extends the connector Node class, or exposes IFile so the copy+delete fallback applies.","As a client workaround, copy the content and delete the source instead of MOVE.","Report the incompatibility to the app maintainer: core cannot move foreign node types."],"exampleFix":"// before: HTTP 400 Incompatible node types\n$targetDir->moveInto($newName, $sourcePath, $foreignNode);\n\n// after: copy + delete fallback for non-connector nodes\nif (!$foreignNode instanceof \\OCA\\DAV\\Connector\\Sabre\\Node) {\n    $targetDir->createFile($newName, $foreignNode->get());\n    $foreignNode->delete();\n} else {\n    $targetDir->moveInto($newName, $sourcePath, $foreignNode);\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isMovableOverDav(\\Sabre\\DAV\\INode $node): bool {\n    return $node instanceof \\OCA\\DAV\\Connector\\Sabre\\Node\n        || $node instanceof \\Sabre\\DAV\\IFile; // copy+delete fallback applies\n}\n\nif (!isMovableOverDav($sourceNode)) {\n    // copy content + delete source instead of MOVE\n    $targetDir->createFile($targetName, $sourceNode->get());\n    $sourceNode->delete();\n} else {\n    $targetDir->moveInto($targetName, $sourcePath, $sourceNode);\n}","tryCatchPattern":"try {\n    $moved = $targetDir->moveInto($targetName, $sourcePath, $sourceNode);\n} catch (BadRequest $e) { // HTTP 400 Incompatible node types\n    $moved = false; // signal Sabre to use its copy+delete fallback\n}","preventionTips":["Third-party DAV apps must extend the connector Node or expose IFile for the copy+delete fallback.","Fall back to copy+delete when MOVE yields 400 Incompatible node types.","Pin app versions compatible with the server's DAV connector contract."],"tags":["dav","move","node-type","bad-request"],"backgroundTag":"type-mismatch","analyzedSha":"ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3","analyzedAt":"2026-08-17T01:36:13.386Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}