mikefarah/yq · error

bad expression, could not find matching `)`

Error message

bad expression, could not find matching `)`

What it means

After the infix-to-postfix conversion finished, validateNoOpenTokens found an openCollect token still on the operator stack — a '[' or (per the sibling case) '(' that was never closed. The message names ')' because the conversion cannot complete a parenthesised group, so the expression is syntactically unbalanced and is rejected before evaluation.

Source

Thrown at pkg/yqlib/expression_postfix.go:34

func newExpressionPostFixer() expressionPostFixer {
	return &expressionPostFixerImpl{}
}

func popOpToResult(opStack []*token, result []*Operation) ([]*token, []*Operation) {
	var newOp *token
	opStack, newOp = opStack[0:len(opStack)-1], opStack[len(opStack)-1]
	log.Debugf("popped %v from opstack to results", newOp.toString(true))
	return opStack, append(result, newOp.Operation)
}

func validateNoOpenTokens(token *token) error {
	switch token.TokenType {
	case openCollect:
		return fmt.Errorf(("bad expression, could not find matching `]`"))
	case openCollectObject:
		return fmt.Errorf(("bad expression, could not find matching `}`"))
	case openBracket:
		return fmt.Errorf(("bad expression, could not find matching `)`"))
	}
	return nil
}

func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Operation, error) {
	var result []*Operation
	// surround the whole thing with brackets
	var opStack = []*token{{TokenType: openBracket}}
	var tokens = append(infixTokens, &token{TokenType: closeBracket})

	for _, currentToken := range tokens {
		log.Debugf("postfix processing currentToken %v", currentToken.toString(true))
		switch currentToken.TokenType {
		case openBracket, openCollect, openCollectObject:
			opStack = append(opStack, currentToken)
			log.Debugf("put %v onto the opstack", currentToken.toString(true))
		case closeCollect, closeCollectObject:
			var opener tokenType = openCollect

View on GitHub (pinned to 8b5af0694b)

Solutions

  1. Add the matching `)` to close the group (e.g. `(.a + .b)`)
  2. Single-quote the expression in shell so parentheses survive
  3. Balance-check parentheses in generated/template expressions

Example fix

// before
yq '(.a + .b' file.yml
// after
yq '(.a + .b)' file.yml
Defensive patterns

Strategy: validation

Validate before calling

depth := strings.Count(expr, "(") - strings.Count(expr, ")")
if depth != 0 {
    return fmt.Errorf("unbalanced parentheses in expression %q", expr)
}

Try / catch

out, err := runYq(expr, file)
if err != nil && strings.Contains(err.Error(), "could not find matching `)`") {
    return fmt.Errorf("unclosed ( in expression %q", expr)
}

Prevention

When it happens

Trigger: Running `yq '(.a + .b' file.yml` — any `(` without its matching `)`, common when parentheses are dropped by bad shell quoting or nested subexpressions.

Common situations: Complex arithmetic/filter expressions in shell scripts where quotes were lost, parenthesised selects like `select(.x == 1)` missing the final `)`.

Related errors


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