AlistGo/alist · error

cannot remove a submodule

Error message

cannot remove a submodule

What it means

During Remove the driver fetches the parent git tree and looks for the entry matching the object's name. A tree entry with Type == "commit" is a git submodule pointer, not a regular blob or tree. The driver cannot compose a valid delete commit for a submodule link, so it explicitly rejects the operation with this error.

Source

Thrown at drivers/github/driver.go:572

	return d.commit(message, rootSha)
}

func (d *Github) Remove(ctx context.Context, obj model.Obj) error {
	if !d.isOnBranch {
		return errors.New("cannot write to non-branch reference")
	}
	d.commitMutex.Lock()
	defer d.commitMutex.Unlock()
	parentDir := stdpath.Dir(obj.GetPath())
	tree, treeSha, err := d.getTreeDirectly(parentDir)
	if err != nil {
		return err
	}
	var del *TreeObjReq = nil
	for _, t := range tree.Trees {
		if t.Path == obj.GetName() {
			if t.Type == "commit" {
				return errors.New("cannot remove a submodule")
			}
			del = &t.TreeObjReq
			del.Sha = nil
			break
		}
	}
	if del == nil {
		return errs.ObjectNotFound
	}
	newTree := make([]interface{}, 0, 2)
	newTree = append(newTree, *del)
	if len(tree.Trees) == 1 { // completely emptying the repository will get a 404
		newTree = append(newTree, map[string]string{
			"path":    ".gitkeep",
			"mode":    "100644",
			"type":    "blob",
			"content": "",
		})

View on GitHub (pinned to 843d9dc814)

Solutions

  1. Remove the submodule with plain git: git rm <path>, delete the .gitmodules entry, commit and push
  2. Edit .gitmodules and the gitlink entry manually via a commit, then let OpenList re-list
  3. If submodules are not wanted, replace them with vendored copies or a package manager

Example fix

# before: delete submodule via OpenList UI -> error
# after: in a local clone
git rm --cached <submodule-path>
git rm <submodule-path>
git commit -m "remove submodule"
git push
Defensive patterns

Strategy: validation

Validate before calling

// Check tree entry type before calling Remove
tree, _, _ := d.getTreeDirectly(stdpath.Dir(obj.GetPath()))
for _, t := range tree.Trees {
	if t.Path == obj.GetName() && t.Type == "commit" {
		return errors.New("path is a submodule; remove it with git")
	}
}
return d.Remove(ctx, obj)

Type guard

func isSubmoduleEntry(t TreeEntry) bool { return t.Type == "commit" }

Try / catch

if err := d.Remove(ctx, obj); err != nil {
	if strings.Contains(err.Error(), "cannot remove a submodule") {
		log.Warnf("%s is a submodule; removing via git instead", obj.GetPath())
		return nil // handled out-of-band
	}
	return err
}

Prevention

When it happens

Trigger: Calling Remove on a directory or file whose corresponding entry in the GitHub tree has type 'commit' — i.e. the path is a submodule defined in .gitmodules; the loop in Remove finds t.Type == "commit" and bails out before building the new tree.

Common situations: A repo vendors dependencies as submodules (common in C/C++ projects, dotfiles frameworks, themes) and the user tries to delete the submodule folder through the OpenList file manager.

Related errors


AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15). Data as JSON: /api/errors/f6cf72bc9810eadc. Report an issue: GitHub.