bazelbuild/bazel · error · ValueError

{tokens[1]} in {line} not surrounded by parentheses

Error message

{tokens[1]} in {line} not surrounded by parentheses

What it means

Thrown by ctexplain's _ParseConfiguredTargetLine while parsing a cquery output line of the form '<label> (<config_hash>) [<fragments>]' or '<label> (null)'. It splits on whitespace (maxsplit=2) and requires the second token to start with '(' and end with ')'; otherwise it raises this ValueError showing the bad token and full line.

Source

Thrown at tools/ctexplain/bazel_api.py:149

  """Converts a cquery output line to a ConfiguredTarget.

  Expected input is:

      "<label> (<config hash>) [configFragment1, configFragment2, ...]"

  or:
      "<label> (null)"

  Args:
    line: The expected input.

  Returns:
    Corresponding ConfiguredTarget if the line matches else None.
  """
  tokens = line.split(maxsplit=2)
  label = tokens[0]
  if tokens[1][0] != "(" or tokens[1][-1] != ")":
    raise ValueError(f"{tokens[1]} in {line} not surrounded by parentheses")
  config_hash = tokens[1][1:-1]
  if config_hash == "null":
    fragments = ()
  else:
    if tokens[2][0] != "[" or tokens[2][-1] != "]":
      raise ValueError(f"{tokens[2]} in {line} not surrounded by [] brackets")
    # The fragments list looks like '[Fragment1, Fragment2, ...]'. Split the
    # whole line on ' [' to get just this list, then remove the final ']', then
    # split again on ', ' to convert it to a structured tuple.
    fragments = tuple(line.split(" [")[1][0:-1].split(", "))
  return ConfiguredTarget(
      label=label,
      config=None,  # Not yet available: we'll need `bazel config` to get this.
      config_hash=config_hash,
      transitive_fragments=fragments)


def _base_name(full_name: str) -> str:

View on GitHub (pinned to e6e199d060)

Solutions

  1. Do not pass --output in the build flags; ctexplain depends on the default 'label (hash) [fragments]' transient format.
  2. Regenerate the input from 'bazel cquery' with no output-mode flags.
  3. If a new bazel version changed the format, update the parser in bazel_api.py to match.

Example fix

# before
bazel.cquery(['deps(//a)', '--output=label_only'])

# after
bazel.cquery(['deps(//a)'])  # keep default output format
Defensive patterns

Strategy: validation

Validate before calling

import re
CQUERY_LINE = re.compile(r'^\S+ \([^)]+\)( .*)?$')
lines = [l for l in cquery_stdout if CQUERY_LINE.match(l)]

Prevention

When it happens

Trigger: Feeding get_config/parse functions a line whose second whitespace token is not parenthesized — e.g. cquery run with a different --output format (label-only, starlark, jsonprogress), or lines from another tool that lack the '(hash)' segment.

Common situations: ctexplain invoked with build_flags that override --output; bazel version changing cquery's default output format; hand-edited or truncated cquery logs.

Related errors


AI-assisted analysis of bazelbuild/bazel@e6e199d060 (2026-08-14). Data as JSON: /api/errors/4bb7db5fe3df66aa. Report an issue: GitHub.