{"record":{"id":"52d1b383aa2a4201","repo":"mikefarah/yq","slug":"cannot-substitute-with-v-can-only-substitute-str","errorCode":null,"errorMessage":"cannot substitute with %v, can only substitute strings. Hint: Most often you'll want to use '|=' over '=' for this operation","messagePattern":"cannot substitute with (.+?), can only substitute strings\\. Hint: Most often you'll want to use '\\|=' over '=' for this operation","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/yqlib/operator_env.go","lineNumber":82,"sourceCode":"\tpreferences := envOpPreferences{}\n\tif expressionNode.Operation.Preferences != nil {\n\t\tpreferences = expressionNode.Operation.Preferences.(envOpPreferences)\n\t}\n\n\tparser := parse.New(\"string\", os.Environ(),\n\t\t&parse.Restrictions{NoUnset: preferences.NoUnset, NoEmpty: preferences.NoEmpty})\n\n\tif preferences.FailFast {\n\t\tparser.Mode = parse.Quick\n\t} else {\n\t\tparser.Mode = parse.AllErrors\n\t}\n\n\tfor el := context.MatchingNodes.Front(); el != nil; el = el.Next() {\n\t\tnode := el.Value.(*CandidateNode)\n\t\tif node.Tag != \"!!str\" {\n\t\t\tlog.Warningf(\"EnvSubstOperator, env name: %v %v\", node.Tag, node.Value)\n\t\t\treturn 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)\n\t\t}\n\n\t\tvalue, err := parser.Parse(node.Value)\n\t\tif err != nil {\n\t\t\treturn Context{}, err\n\t\t}\n\t\tresult := node.CreateReplacement(ScalarNode, \"!!str\", value)\n\t\tresults.PushBack(result)\n\t}\n\n\treturn context.ChildContext(results), nil\n}\n","sourceCodeStart":64,"sourceCodeEnd":95,"githubUrl":"https://github.com/mikefarah/yq/blob/8b5af0694bb82b41d4ae180fac9972029066f90a/pkg/yqlib/operator_env.go#L64-L95","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Use `|=` instead of `=`: `.x |= envsubst(.x)` so substitution runs on the matched string value","Ensure the target node is a string: quote it in YAML (`port: \"${PORT}\"`) or convert with `| tostring`","Apply envsubst only to string fields, not maps/scalars/numbers","Use string interpolation `\"\\(env(MY))\"` semantics or strenv where appropriate"],"exampleFix":"// before\n.port = envsubst(.port)   # .port is !!int\n// error: cannot substitute with !!int ...\n\n// after\n.port |= envsubst(.port | tostring)\n# or keep the YAML value quoted so it stays a string","handlingStrategy":"type-guard","validationCode":"yq '.x | tag' file.yaml   # must print !!str before envsubst","typeGuard":"def is_string_node(node):\n    return getattr(node, 'tag', '') == '!!str'","tryCatchPattern":"out=$(yq '.x |= envsubst(.x)' f.yaml 2>&1) || {\n  echo \"envsubst target not a string: $out\" >&2\n  exit 1\n}","preventionTips":["Quote template values in YAML so they parse as strings","Prefer |= over = with envsubst","Coerce with tostring before substituting","Only apply envsubst to string fields"],"tags":["yq","environment","type-mismatch"],"backgroundTag":"envsubst-non-string","analyzedSha":"8b5af0694bb82b41d4ae180fac9972029066f90a","analyzedAt":"2026-09-05T10:57:22.766Z","contentChangedAt":"2026-09-05T10:57:22.766Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}