larksuite/cli · error

attachment part %q not found

Error message

attachment part %q not found

What it means

This error means the draft's MIME snapshot has a body, but no MIME part with the requested part_id exists, so the attachment removal cannot proceed. removeAttachment is a mail draft patch operation that looks up the target part with findPart before deleting it. The library throws it to prevent silently ignoring a remove op that references a nonexistent part.

Source

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

	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)
	}
	if snapshot.Body.PartID == partID {
		return fmt.Errorf("cannot remove root MIME part")
	}
	removed := removePart(snapshot.Body, partID)
	if !removed {
		return fmt.Errorf("attachment part %q not found", partID)
	}
	return nil
}

func removePart(parent *Part, targetPartID string) bool {
	for idx, child := range parent.Children {
		if child == nil {
			continue

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Re-read the draft to get the current MIME snapshot and use a part_id that exists
  2. Check whether the op already ran earlier in the same patch and remove the duplicate op
  3. Verify part_id spelling/format against the draft's listed attachment parts
  4. Use the inline-part remove operation if the part is actually an inline part (see related error)

Example fix

// before
{"op":"remove_attachment","target":{"part_id":"old-stale-id"}}
// after — re-fetch draft, list attachments, use a live part_id
{"op":"remove_attachment","target":{"part_id":"current-attachment-part-id"}}
Defensive patterns

Strategy: validation

Validate before calling

func canRemoveAttachment(snap *DraftSnapshot, partID string) bool {
	part := findPart(snap.Body, partID)
	return snap.Body != nil && part != nil && !strings.EqualFold(part.ContentDisposition, "inline") && part.ContentID == "" && snap.Body.PartID != partID
}

Type guard

func isAttachmentPart(p *Part) bool {
	return p != nil && !strings.EqualFold(p.ContentDisposition, "inline") && p.ContentID == ""
}

Try / catch

if err := applyOp(snap, op); err != nil && strings.Contains(err.Error(), "not found") {
	// re-fetch draft snapshot and re-resolve part_id before retrying
}

Prevention

When it happens

Trigger: Calling the mail draft patch remove_attachment operation with a target part_id that is not present in the current draft MIME tree, or one that was already removed by a previous op in the same patch batch.

Common situations: Stale part IDs from an earlier draft read after the draft was regenerated; double-applying the same patch; typos in part_id; removing attachments from a draft whose body was replaced between reads.

Related errors


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