larksuite/cli · error

remove_attachment: %w

Error message

remove_attachment: %w

What it means

This is a wrapper around a resolveTarget failure during the 'remove_attachment' patch op. resolveTarget could not map the op's Target (part ID or locator) to an existing attachment part in the draft snapshot; the underlying cause is preserved via %w.

Source

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

		if err := ensureHeaderEditable(op.Name, options); err != nil {
			return err
		}
		removeHeader(&snapshot.Headers, op.Name)
	case "add_attachment":
		return addAttachment(dctx, snapshot, op.Path)
	case "remove_attachment":
		// Priority: part_id > cid > token. When only token is set, route to
		// the large attachment path (updates header + HTML card, no MIME
		// part to remove). Otherwise, resolve to a concrete part_id.
		tgt := op.Target
		if strings.TrimSpace(tgt.PartID) == "" && strings.TrimSpace(tgt.CID) == "" {
			if token := strings.TrimSpace(tgt.Token); token != "" {
				return removeLargeAttachment(snapshot, token)
			}
		}
		partID, err := resolveTarget(snapshot, tgt)
		if err != nil {
			return fmt.Errorf("remove_attachment: %w", err)
		}
		return removeAttachment(snapshot, partID)
	case "add_inline":
		return addInline(dctx, snapshot, op.Path, op.CID, op.FileName, op.ContentType)
	case "replace_inline":
		partID, err := resolveTarget(snapshot, op.Target)
		if err != nil {
			return fmt.Errorf("replace_inline: %w", err)
		}
		return replaceInline(dctx, snapshot, partID, op.Path, op.CID, op.FileName, op.ContentType)
	case "remove_inline":
		partID, err := resolveTarget(snapshot, op.Target)
		if err != nil {
			return fmt.Errorf("remove_inline: %w", err)
		}
		return removeInline(snapshot, partID)
	case "insert_signature":
		return insertSignatureOp(snapshot, op)

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Print/check the wrapped cause (errors.Unwrap or %v) to see the exact resolve failure.
  2. Re-fetch a fresh DraftSnapshot before applying the removal.
  3. Verify the Target partID exists in snapshot.Parts (or equivalent) before patching.
  4. For token-based large attachments, ensure Token is set so the large-attachment path is used.

Example fix

// before
ops := []PatchOp{{Op: "remove_attachment", Target: Target{PartID: staleID}}}
// after
snapshot := fetchFreshSnapshot(dctx)
if partExists(snapshot, partID) {
    ops := []PatchOp{{Op: "remove_attachment", Target: Target{PartID: partID}}}
}
Defensive patterns

Strategy: validation

Validate before calling

func canRemoveAttachment(snap *DraftSnapshot, tgt Target) error {
    if strings.TrimSpace(tgt.Token) != "" { return nil } // large-attachment path
    _, err := resolveTarget(snap, tgt)
    return err
}

Type guard

func targetExists(snap *DraftSnapshot, tgt Target) bool {
    _, err := resolveTarget(snap, tgt)
    return err == nil
}

Try / catch

if err := Apply(ctx, dctx, ops, opts); err != nil {
    var unwrapped error = err
    for errors.Unwrap(unwrapped) != nil { unwrapped = errors.Unwrap(unwrapped) }
    if strings.HasPrefix(err.Error(), "remove_attachment:") {
        return fmt.Errorf("target unresolvable (%v); refresh snapshot", unwrapped)
    }
    return err
}

Prevention

When it happens

Trigger: Calling Apply with PatchOp{Op:"remove_attachment"} whose Target references a partID that does not exist, was already removed, or whose locator format is invalid. Note: a non-empty Token goes through removeLargeAttachment and never reaches this error.

Common situations: Patching a stale snapshot after another patch already removed the attachment; typo in partID; applying remove_attachment to a draft that never had the attachment.

Related errors


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