AlistGo/alist · error
cannot copy a submodule
Error message
cannot copy a submodule
What it means
In the copy helper used by Copy/Move, after fetching the source parent tree the driver scans for the source entry; an entry of Type == "commit" is a git submodule (gitlink). Git submodule pointers reference commits in foreign repositories and cannot be duplicated into an arbitrary destination tree by this driver, so the copy is rejected.
Source
Thrown at drivers/github/driver.go:903
func (d *Github) copyWithoutRenewTree(srcObj, dstDir model.Obj) (dstSha, newSha, srcParentSha string, srcParentTree *TreeResp, err error) {
dst, err := d.get(dstDir.GetPath())
if err != nil {
return "", "", "", nil, err
}
if dst.Entries == nil {
return "", "", "", nil, errs.NotFolder
}
dstSha = dst.Sha
srcParentPath := stdpath.Dir(srcObj.GetPath())
srcParentTree, srcParentSha, err = d.getTreeDirectly(srcParentPath)
if err != nil {
return "", "", "", nil, err
}
var src *TreeObjReq = nil
for _, t := range srcParentTree.Trees {
if t.Path == srcObj.GetName() {
if t.Type == "commit" {
return "", "", "", nil, errors.New("cannot copy a submodule")
}
src = &t.TreeObjReq
break
}
}
if src == nil {
return "", "", "", nil, errs.ObjectNotFound
}
newTree := make([]interface{}, 0, 2)
newTree = append(newTree, *src)
if len(dst.Entries) == 1 && dst.Entries[0].Name == ".gitkeep" {
newTree = append(newTree, TreeObjReq{
Path: ".gitkeep",
Mode: "100644",
Type: "blob",
Sha: nil,
})View on GitHub (pinned to 843d9dc814)
Solutions
- Clone locally and use git: move the submodule path, update .gitmodules, commit, push
- Copy the submodule's upstream URL and re-add it at the destination with git submodule add
- Replace the submodule with plain vendored files if it must be copyable through the driver
Example fix
# before: Copy submodule-folder -> error # after: in a local clone git submodule deinit -f <old-path> && git rm <old-path> git submodule add <url> <new-path> git commit -m "move submodule" && git push
Defensive patterns
Strategy: validation
Validate before calling
// Before Copy/Move, verify the source is not a submodule
tree, _, err := d.getTreeDirectly(stdpath.Dir(src.GetPath()))
if err != nil { return err }
for _, t := range tree.Trees {
if t.Path == src.GetName() && t.Type == "commit" {
return errors.New("source is a submodule; copy it with git instead")
}
}
return d.Copy(ctx, src, dst) Type guard
func isSubmoduleEntry(t TreeEntry) bool { return t.Type == "commit" } Try / catch
if err := d.Copy(ctx, src, dst); err != nil {
if strings.Contains(err.Error(), "cannot copy a submodule") {
return fmt.Errorf("%s is a submodule and must be copied via git", src.GetPath())
}
return err
} Prevention
- Mark submodule directories in listings so copy/move UI can disable them
- Script submodule relocation with git submodule add/deinit
- Keep .gitmodules in review checklists so submodules are never copied accidentally
When it happens
Trigger: Calling Copy or Move where srcObj is a submodule directory — the tree walk hits t.Type == "commit" for the matching path and returns before any tree mutation.
Common situations: Trying to duplicate or relocate a vendored submodule (themes, SDKs) into another folder of the same repo via the OpenList UI.
Related errors
- cannot remove a submodule
- cannot download a submodule
- cannot move a submodule
- cannot rename a submodule
- cannot copy parent dir to child
AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15).
Data as JSON: /api/errors/a6f66e2599858534.
Report an issue: GitHub.