{"record":{"id":"4bb7db5fe3df66aa","repo":"bazelbuild/bazel","slug":"tokens-1-in-line-not-surrounded-by-parenthese","errorCode":null,"errorMessage":"{tokens[1]} in {line} not surrounded by parentheses","messagePattern":"(.+?) in (.+?) not surrounded by parentheses","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"tools/ctexplain/bazel_api.py","lineNumber":149,"sourceCode":"  \"\"\"Converts a cquery output line to a ConfiguredTarget.\n\n  Expected input is:\n\n      \"<label> (<config hash>) [configFragment1, configFragment2, ...]\"\n\n  or:\n      \"<label> (null)\"\n\n  Args:\n    line: The expected input.\n\n  Returns:\n    Corresponding ConfiguredTarget if the line matches else None.\n  \"\"\"\n  tokens = line.split(maxsplit=2)\n  label = tokens[0]\n  if tokens[1][0] != \"(\" or tokens[1][-1] != \")\":\n    raise ValueError(f\"{tokens[1]} in {line} not surrounded by parentheses\")\n  config_hash = tokens[1][1:-1]\n  if config_hash == \"null\":\n    fragments = ()\n  else:\n    if tokens[2][0] != \"[\" or tokens[2][-1] != \"]\":\n      raise ValueError(f\"{tokens[2]} in {line} not surrounded by [] brackets\")\n    # The fragments list looks like '[Fragment1, Fragment2, ...]'. Split the\n    # whole line on ' [' to get just this list, then remove the final ']', then\n    # split again on ', ' to convert it to a structured tuple.\n    fragments = tuple(line.split(\" [\")[1][0:-1].split(\", \"))\n  return ConfiguredTarget(\n      label=label,\n      config=None,  # Not yet available: we'll need `bazel config` to get this.\n      config_hash=config_hash,\n      transitive_fragments=fragments)\n\n\ndef _base_name(full_name: str) -> str:","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/bazelbuild/bazel/blob/e6e199d0601a244511b4cf18c8b2828aa73db1fd/tools/ctexplain/bazel_api.py#L131-L167","documentation":"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.","triggerScenarios":"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.","commonSituations":"ctexplain invoked with build_flags that override --output; bazel version changing cquery's default output format; hand-edited or truncated cquery logs.","solutions":["Do not pass --output in the build flags; ctexplain depends on the default 'label (hash) [fragments]' transient format.","Regenerate the input from 'bazel cquery' with no output-mode flags.","If a new bazel version changed the format, update the parser in bazel_api.py to match."],"exampleFix":"# before\nbazel.cquery(['deps(//a)', '--output=label_only'])\n\n# after\nbazel.cquery(['deps(//a)'])  # keep default output format","handlingStrategy":"validation","validationCode":"import re\nCQUERY_LINE = re.compile(r'^\\S+ \\([^)]+\\)( .*)?$')\nlines = [l for l in cquery_stdout if CQUERY_LINE.match(l)]","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never pass --output flags through build_flags to ctexplain.","Pin the bazel version whose cquery format the parser expects."],"tags":["ctexplain","cquery","parsing","bazel"],"backgroundTag":null,"analyzedSha":"e6e199d0601a244511b4cf18c8b2828aa73db1fd","analyzedAt":"2026-08-14T10:24:27.848Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}