larksuite/cli · error

set_header: header value must not contain CR or LF

Error message

set_header: header value must not contain CR or LF

What it means

applyOp validates that a 'set_header' op's value contains no CR or LF. Unencoded newlines in a header value would inject extra headers, so the library rejects the op.

Source

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

	case "clear_reply_to":
		removeHeader(&snapshot.Headers, "Reply-To")
	case "set_body":
		return setBody(snapshot, op.Value, options)
	case "set_reply_body":
		return setReplyBody(snapshot, op.Value, options)
	case "replace_body":
		return replaceBody(snapshot, op.BodyKind, op.Value, options)
	case "append_body":
		return appendBody(snapshot, op.BodyKind, op.Value, options)
	case "set_header":
		if err := ensureHeaderEditable(op.Name, options); err != nil {
			return err
		}
		if strings.ContainsAny(op.Name, ":\r\n") {
			return fmt.Errorf("set_header: header name must not contain ':', CR, or LF")
		}
		if strings.ContainsAny(op.Value, "\r\n") {
			return fmt.Errorf("set_header: header value must not contain CR or LF")
		}
		upsertHeader(&snapshot.Headers, op.Name, op.Value)
	case "remove_header":
		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)
			}

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Sanitize with strings.TrimSpace or replace newlines before building the op.
  2. For multi-line data, use RFC 2047 encoded words or folded header syntax produced by an encoding library instead of raw '\n'.
  3. Reject the input at your API boundary before constructing the patch.

Example fix

// before
op := PatchOp{Op: "set_header", Name: "X-Note", Value: multiLineText}
// after
op := PatchOp{Op: "set_header", Name: "X-Note", Value: strings.ReplaceAll(strings.ReplaceAll(multiLineText, "\r", " "), "\n", " ")}
Defensive patterns

Strategy: validation

Validate before calling

func validHeaderValue(v string) bool { return !strings.ContainsAny(v, "\r\n") }
// before op: if !validHeaderValue(value) { sanitize or reject }

Type guard

func sanitizeHeaderValue(v string) string {
    return strings.TrimSpace(strings.ReplaceAll(strings.ReplaceAll(v, "\r", " "), "\n", " "))
}

Try / catch

if err := Apply(ctx, dctx, ops, opts); err != nil {
    if strings.Contains(err.Error(), "header value must not contain CR or LF") {
        return fmt.Errorf("collapse newlines in header value: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling Apply with PatchOp{Op:"set_header"} where Value contains '\r' or '\n', e.g. a multi-line value read from user input or a config file.

Common situations: Setting tracking/reference headers from copied text with line breaks; values imported from JSON or CSV with embedded newlines; template output containing newlines.

Related errors


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