larksuite/cli · error

cannot remove root MIME part

Error message

cannot remove root MIME part

What it means

removeInline throws this when the requested partID is the draft body's root MIME part (multipart/mixed or similar). The root container holds all other parts, so deleting it would destroy the message structure and is disallowed.

Source

Thrown at shortcuts/mail/draft/patch.go:742

	part.ContentDispositionArg = map[string]string{"filename": fileName}
	part.ContentID = finalCID
	part.TransferEncoding = "base64"
	part.Body = content
	part.Dirty = true
	syncStructuredPartHeaders(part)
	return nil
}

func removeInline(snapshot *DraftSnapshot, partID string) error {
	part := findPart(snapshot.Body, partID)
	if part == nil {
		return fmt.Errorf("inline part %q not found", partID)
	}
	if !isInlinePart(part) {
		return fmt.Errorf("part %q is not an inline MIME part", partID)
	}
	if snapshot.Body == nil || snapshot.Body.PartID == partID {
		return fmt.Errorf("cannot remove root MIME part")
	}
	if !removePart(snapshot.Body, partID) {
		return fmt.Errorf("inline part %q not found", partID)
	}
	return nil
}

func removeAttachment(snapshot *DraftSnapshot, partID string) error {
	if snapshot.Body == nil {
		return fmt.Errorf("draft has no MIME body")
	}
	part := findPart(snapshot.Body, partID)
	if part == nil {
		return fmt.Errorf("attachment part %q not found", partID)
	}
	if strings.EqualFold(part.ContentDisposition, "inline") || part.ContentID != "" {
		return fmt.Errorf("part %q is an inline MIME part; use remove_inline", partID)
	}

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Skip the root part ID (compare against the body's own PartID) when iterating parts
  2. Remove only leaf inline parts; the root is managed implicitly
  3. Clear the whole body via the body-level operation instead of removing the root part

Example fix

// before
for id in $(all part ids); do --remove-inline part_id=$id; done
// after
for id in $(leaf inline part ids only); do --remove-inline part_id=$id; done
Defensive patterns

Strategy: validation

Validate before calling

snap, _ := draftGet(draftID)
if snap.Body != nil && snap.Body.PartID == partID {
    return errors.New("refusing to remove root MIME part")
}

Type guard

func isRootPart(snap *DraftSnapshot, id string) bool {
    return snap != nil && snap.Body != nil && snap.Body.PartID == id
}

Try / catch

if err := removeInline(...); err != nil {
    if strings.Contains(err.Error(), "cannot remove root MIME part") {
        // skip this ID in bulk-removal loops
    }
}

Prevention

When it happens

Trigger: applyOp -> removeInline where snapshot.Body.PartID == partID (or Body is nil while the ID matches), i.e. the caller passed the root part ID.

Common situations: Iterating all part IDs from a draft get and attempting to remove every one, including the root; assuming the root is a removable inline part.

Related errors


AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04). Data as JSON: /api/errors/cc8c298565f4385f. Report an issue: GitHub.