mikefarah/yq · error

cannot substitute with %v, can only substitute strings. Hint

Error message

cannot substitute with %v, can only substitute strings. Hint: Most often you'll want to use '|=' over '=' for this operation

What it means

envsubst can only interpolate environment variables into string nodes. If the node it is applied to has any tag other than "!!str" (number, boolean, map, null, etc.), yq refuses rather than coercing types. The message also hints that assignments like `.x = envsubst(...)` often should be `.x |= envsubst(...)` so the substitution applies to the existing string value.

Source

Thrown at pkg/yqlib/operator_env.go:82

	preferences := envOpPreferences{}
	if expressionNode.Operation.Preferences != nil {
		preferences = expressionNode.Operation.Preferences.(envOpPreferences)
	}

	parser := parse.New("string", os.Environ(),
		&parse.Restrictions{NoUnset: preferences.NoUnset, NoEmpty: preferences.NoEmpty})

	if preferences.FailFast {
		parser.Mode = parse.Quick
	} else {
		parser.Mode = parse.AllErrors
	}

	for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
		node := el.Value.(*CandidateNode)
		if node.Tag != "!!str" {
			log.Warningf("EnvSubstOperator, env name: %v %v", node.Tag, node.Value)
			return Context{}, fmt.Errorf("cannot substitute with %v, can only substitute strings. Hint: Most often you'll want to use '|=' over '=' for this operation", node.Tag)
		}

		value, err := parser.Parse(node.Value)
		if err != nil {
			return Context{}, err
		}
		result := node.CreateReplacement(ScalarNode, "!!str", value)
		results.PushBack(result)
	}

	return context.ChildContext(results), nil
}

View on GitHub (pinned to 8b5af0694b)

Solutions

  1. Use `|=` instead of `=`: `.x |= envsubst(.x)` so substitution runs on the matched string value
  2. Ensure the target node is a string: quote it in YAML (`port: "${PORT}"`) or convert with `| tostring`
  3. Apply envsubst only to string fields, not maps/scalars/numbers
  4. Use string interpolation `"\(env(MY))"` semantics or strenv where appropriate

Example fix

// before
.port = envsubst(.port)   # .port is !!int
// error: cannot substitute with !!int ...

// after
.port |= envsubst(.port | tostring)
# or keep the YAML value quoted so it stays a string
Defensive patterns

Strategy: type-guard

Validate before calling

yq '.x | tag' file.yaml   # must print !!str before envsubst

Type guard

def is_string_node(node):
    return getattr(node, 'tag', '') == '!!str'

Try / catch

out=$(yq '.x |= envsubst(.x)' f.yaml 2>&1) || {
  echo "envsubst target not a string: $out" >&2
  exit 1
}

Prevention

When it happens

Trigger: Running `.x = envsubst(.x)` where .x is a number/bool/map (its tag isn't !!str); applying envsubst to null nodes; using `=` (which re-evaluates RHS against the root) instead of `|=` so the RHS selects a non-string node.

Common situations: Substituting into numeric ports/timeouts; forgetting |= as hinted in the message; envsubst against YAML that parsed the value as a number or boolean; applying envsubst to whole documents rather than string fields.

Related errors


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