googleapis/mcp-toolbox · error

unclosed backtick identifier

Error message

unclosed backtick identifier

What it means

parseIdentifierSequence parses dotted identifiers and supports backtick-quoted parts. When a part starts with a backtick but no closing backtick exists anywhere after it, the identifier is unterminated and cannot be extracted, so parsing fails with this error.

Solutions

  1. Add the missing closing backtick to the quoted identifier
  2. Remove backticks entirely if the identifier contains no special characters (project/dataset/table names are usually unquoted-safe)
  3. Check that templating/escaping layers (YAML, shell) are not eating the closing backtick
  4. Use straight ASCII backticks, not typographic quotes

Example fix

// before
SELECT * FROM `proj.ds.t;
// after
SELECT * FROM `proj.ds.t`;
Defensive patterns

Strategy: validation

Validate before calling

func backticksClosed(sql string) bool {
    return strings.Count(sql, "`")%2 == 0
}

Try / catch

if !backticksClosed(sql) {
    return fmt.Errorf("query has an unclosed backtick identifier")
}
_, err := parser.Parse(sql)
if err != nil {
    return err
}

Prevention

When it happens

Trigger: A table name like `project.dataset.table` missing the closing backtick passed through parseSQL, e.g. `proj`.`ds.t or a lone stray backtick before a table name.

Common situations: String templates that forget to close backticks; quotes stripped by shell or YAML escaping; copy-paste from docs that mangles backticks; accidental smart-quote substitution.

Related errors


AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05). Data as JSON: /api/errors/42d42d0e14035220. Report an issue: GitHub.

Appendix: source

Thrown at internal/tools/bigquery/bigquerycommon/table_name_parser.go:421

	var totalConsumed int

	for {
		remaining := s[totalConsumed:]
		trimmed := strings.TrimLeftFunc(remaining, unicode.IsSpace)
		totalConsumed += len(remaining) - len(trimmed)
		current := s[totalConsumed:]

		if len(current) == 0 {
			break
		}

		var part string
		var consumed int

		if current[0] == '`' {
			end := strings.Index(current[1:], "`")
			if end == -1 {
				return nil, 0, fmt.Errorf("unclosed backtick identifier")
			}
			part = current[1 : end+1]
			consumed = end + 2
		} else if len(current) > 0 && unicode.IsLetter(rune(current[0])) {
			end := strings.IndexFunc(current, func(r rune) bool {
				return !unicode.IsLetter(r) && !unicode.IsNumber(r) && r != '_' && r != '-'
			})
			if end == -1 {
				part = current
				consumed = len(current)
			} else {
				part = current[:end]
				consumed = end
			}
		} else {
			break
		}

View on GitHub (pinned to 8cc6e09de2)