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 {
continueView on GitHub (pinned to 7fd6ef3c07)
Solutions
- Re-read the draft to get the current MIME snapshot and use a part_id that exists
- Check whether the op already ran earlier in the same patch and remove the duplicate op
- Verify part_id spelling/format against the draft's listed attachment parts
- 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
- Always re-read the draft snapshot immediately before patching
- Track part IDs per snapshot version; never reuse IDs across reads
- Deduplicate ops within one patch batch
- Classify parts (attachment vs inline) before choosing the op
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
- no part with cid %q found
- MIME nesting too deep (max %d levels)
- inline part %q not found
- part %q is an inline MIME part; use remove_inline
- inline cid is empty
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/b371b2ca372f88e7.
Report an issue: GitHub.