{"record":{"id":"2f843e316094a24c","repo":"AlistGo/alist","slug":"cannot-move-a-submodule","errorCode":null,"errorMessage":"cannot move a submodule","messagePattern":"cannot move a submodule","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"drivers/github/driver.go","lineNumber":310,"sourceCode":"\t\tancestorNewSha, err := d.newTree(ancestorOldSha, []interface{}{*delSrc, *dstNextTree})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trootSha, err = d.renewParentTrees(srcParentPath, ancestorOldSha, ancestorNewSha, \"/\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else if strings.HasPrefix(srcObj.GetPath(), dstDir.GetPath()) { // /aa/bb/1 -> /aa/\n\t\tsrcParentPath := stdpath.Dir(srcObj.GetPath())\n\t\tsrcParentTree, srcParentOldSha, err := d.getTreeDirectly(srcParentPath)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tvar src *TreeObjReq = nil\n\t\tfor _, t := range srcParentTree.Trees {\n\t\t\tif t.Path == srcObj.GetName() {\n\t\t\t\tif t.Type == \"commit\" {\n\t\t\t\t\treturn errors.New(\"cannot move a submodule\")\n\t\t\t\t}\n\t\t\t\tsrc = &t.TreeObjReq\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif src == nil {\n\t\t\treturn errs.ObjectNotFound\n\t\t}\n\n\t\tdelSrc := *src\n\t\tdelSrc.Sha = nil\n\t\tdelSrcTree := make([]interface{}, 0, 2)\n\t\tdelSrcTree = append(delSrcTree, delSrc)\n\t\tif len(srcParentTree.Trees) == 1 {\n\t\t\tdelSrcTree = append(delSrcTree, map[string]string{\n\t\t\t\t\"path\":    \".gitkeep\",\n\t\t\t\t\"mode\":    \"100644\",\n\t\t\t\t\"type\":    \"blob\",","sourceCodeStart":292,"sourceCodeEnd":328,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/drivers/github/driver.go#L292-L328","documentation":"During Move()'s handling of the /aa/bb/1 -> /aa/ case, the driver scans the source parent tree for the entry to move and finds its type is 'commit' — a git submodule pointer. Submodules cannot be relocated via the tree API because the pointer references another repository, so the move is rejected. drivers/github/driver.go:310.","triggerScenarios":"Moving an object upward out of its parent where the located tree entry has Type == \"commit\" (submodule).","commonSituations":"Repos with vendored submodules where a user tries to reorganize directories through the file browser; submodule entries surfacing in listings and being treated like normal folders.","solutions":["Perform the reorganization with git (edit .gitmodules, git mv) and push, rather than through the driver.","Leave submodule entries untouched in the mounted view.","Hide submodule entries from listings if they cause confusion."],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"// check tree entry type before moving\nfor _, t := range srcParentTree.Trees {\n    if t.Path == srcObj.GetName() && t.Type == \"commit\" {\n        return errors.New(\"submodule entries cannot be moved via the tree API\")\n    }\n}","typeGuard":"func isCommitEntry(t TreeObj) bool { return t.Type == \"commit\" }","tryCatchPattern":"if err := d.Move(ctx, src, dst); err != nil && strings.Contains(err.Error(), \"submodule\") {\n    // fall back to git-side reorganization\n}","preventionTips":["Exclude submodule entries from move/copy UI actions.","Do submodule reorganization with git mv + .gitmodules edits.","Flag type=='commit' entries in listings as restricted."],"tags":["github","submodule","move","tree-api","go"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}