mikefarah/yq · error

strings cannot be subtracted

Error message

strings cannot be subtracted

What it means

yq refuses to subtract strings: unlike jq (which errors differently) or some shells, `-` on `!!str` operands has no meaning in yq, so subtractScalars returns this error whenever the resolved lhs tag is `!!str` and the value is not being treated as a datetime. Note the asymmetry in the message: it is raised from the subtract path even though the text says 'subtracted'.

Source

Thrown at pkg/yqlib/operator_subtract.go:97

		lhsIsCustom = true
	}

	if !strings.HasPrefix(rhsTag, "!!") {
		// custom tag - we have to have a guess
		rhsTag = rhs.guessTagFromCustomType()
	}

	isDateTime := lhsTag == "!!timestamp"
	// if the lhs is a string, it might be a timestamp in a custom format.
	if lhsTag == "!!str" && context.GetDateTimeLayout() != time.RFC3339 {
		_, err := parseDateTime(context.GetDateTimeLayout(), lhs.Value)
		isDateTime = err == nil
	}

	if isDateTime {
		return subtractDateTime(context.GetDateTimeLayout(), target, lhs, rhs)
	} else if lhsTag == "!!str" {
		return fmt.Errorf("strings cannot be subtracted")
	} else if lhsTag == "!!int" && rhsTag == "!!int" {
		format, lhsNum, err := parseInt64(lhs.Value)
		if err != nil {
			return err
		}
		_, rhsNum, err := parseInt64(rhs.Value)
		if err != nil {
			return err
		}
		result := lhsNum - rhsNum
		target.Tag = lhs.Tag
		target.Value = fmt.Sprintf(format, result)
	} else if (lhsTag == "!!int" || lhsTag == "!!float") && (rhsTag == "!!int" || rhsTag == "!!float") {
		lhsNum, err := strconv.ParseFloat(lhs.Value, 64)
		if err != nil {
			return err
		}
		rhsNum, err := strconv.ParseFloat(rhs.Value, 64)

View on GitHub (pinned to 8b5af0694b)

Solutions

  1. Convert strings to numbers first with `to_number` (or `| tonumber`-style): `(.a | to_number) - (.b | to_number)`
  2. Unquote the values in the source YAML so they parse as !!int/!!float
  3. For string manipulation use the appropriate string operators/functions (sub/split, capture, etc.), not `-`
  4. If the string is actually a datetime, set the right layout with the datetime layout option (via `env`/timestamp functions) so it takes the datetime path

Example fix

// before: yq '"10" - "4"'  -> strings cannot be subtracted
// after
yq '("10" | to_number) - ("4" | to_number)'
Defensive patterns

Strategy: type-guard

Validate before calling

tag_lhs=$(yq '.a | tag' file.yml)
[ "$tag_lhs" = "!!str" ] && echo "strings cannot be subtracted; convert with to_number" && exit 1

Type guard

// Go-side guard before building the expression
func isNumericScalar(s string) bool {
    _, errInt := strconv.ParseInt(s, 10, 64)
    _, errFloat := strconv.ParseFloat(s, 64)
    return errInt == nil || errFloat == nil
}

Try / catch

null

Prevention

When it happens

Trigger: `yq '.name - .other'` where both fields are strings; `.a - .b` where .a is a quoted number like `"10"` (tag !!str) and no custom datetime layout applies; custom-typed nodes guessed as strings.

Common situations: YAML files with quoted numbers (e.g. version: "1.2") that the user expects to be numeric; attempting string 'removal' like removing a prefix (substrings are not supported by `-`); fields tagged !!str due to explicit quoting in the source document.

Related errors


AI-assisted analysis of mikefarah/yq@8b5af0694b (2026-09-05). Data as JSON: /api/errors/da10d8d540da97dc. Report an issue: GitHub.