{"record":{"id":"612dd82aea97d729","repo":"apple/pkl","slug":"invalidglobunclosedsubpattern","errorCode":"invalidGlobUnclosedSubpattern","errorMessage":"invalidGlobUnclosedSubpattern","messagePattern":"invalidGlobUnclosedSubpattern","errorType":"error_code","errorClass":"InvalidGlobPatternException","httpStatus":null,"severity":"error","filePath":"pkl-core/src/main/java/org/pkl/core/util/GlobResolver.java","lineNumber":237,"sourceCode":"            throw new InvalidGlobPatternException(ErrorMessages.create(\"invalidGlobExtGlob\"));\n          }\n          sb.append(\"\\\\+\");\n        }\n        case '!' -> {\n          var next = getNextChar(globPattern, i);\n          if (next == '(') {\n            throw new InvalidGlobPatternException(ErrorMessages.create(\"invalidGlobExtGlob\"));\n          }\n          sb.append(\"!\");\n        }\n\n        // no special meaning in glob patterns but have special meaning in regex.\n        case '.', '(', '%', '^', '$', '|' -> sb.append(\"\\\\\").append(current);\n        default -> sb.append(current);\n      }\n    }\n    if (inGroup) {\n      throw new InvalidGlobPatternException(\"invalidGlobUnclosedSubpattern\");\n    }\n    return sb.append(\"$\").toString();\n  }\n\n  private static void resolveOpaqueGlob(\n      SecurityManager securityManager,\n      ReaderBase reader,\n      URI globUri,\n      Pattern pattern,\n      Map<String, ResolvedGlobElement> result)\n      throws IOException, SecurityManagerException, ExternalReaderProcessException {\n    var elements = reader.listElements(securityManager, globUri);\n    for (var elem : sorted(elements)) {\n      URI resolvedUri;\n      try {\n        resolvedUri = new URI(globUri.getScheme(), elem.getName(), null);\n      } catch (URISyntaxException e) {\n        throw new IllegalArgumentException(e.getMessage(), e);","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/apple/pkl/blob/f3efcbfc9b60d30053b0536d664948d7aa1b8673/pkl-core/src/main/java/org/pkl/core/util/GlobResolver.java#L219-L255","documentation":"GlobResolver supports brace groups `{...}` in globs; when translation finishes and a `{` was opened but never closed with `}`, the pattern is rejected with invalidGlobUnclosedSubpattern. This prevents a partially formed alternation from producing a wrong regex.","triggerScenarios":"A glob with an unterminated `{`, e.g. \"{a,b/ or \"foo{{.pkl\", passed to toRegexPattern/toRegexString via glob import or resource globbing.","commonSituations":"Typos when hand-writing brace expansions, programmatically built patterns where a variable holding the closing brace was empty, or truncation of the pattern string.","solutions":["Close the brace group: add the missing `}`","Remove the stray `{` if no alternation was intended (it is otherwise emitted literally)","Validate the pattern's braces are balanced before calling the API"],"exampleFix":"// before\nglob(\"{src,test/**.pkl\")\n// after\nglob(\"{src,test}/**.pkl\")","handlingStrategy":"validation","validationCode":"function bracesBalanced(g) {\n  let depth = 0;\n  for (const c of g) {\n    if (c === '{') depth++;\n    if (c === '}') depth--;\n    if (depth < 0) return false;\n  }\n  return depth === 0;\n}","typeGuard":"const isClosedGlob = (p) => [...p].filter(c => c === '{').length === [...p].filter(c => c === '}').length;","tryCatchPattern":"try {\n  var regex = GlobResolver.toRegexPattern(glob);\n} catch (InvalidGlobPatternException e) {\n  if (e.getMessage().contains(\"UnclosedSubpattern\")) throw new IllegalArgumentException(\"Unclosed { in glob: \" + glob);\n  throw e;\n}","preventionTips":["Count braces when building patterns programmatically","Lint globs for balanced { } before use","Avoid interpolating untrusted strings into brace groups"],"tags":["glob","unbalanced-braces","pkl"],"backgroundTag":"invalid-regex-pattern","analyzedSha":"f3efcbfc9b60d30053b0536d664948d7aa1b8673","analyzedAt":"2026-09-08T13:10:45.570Z","contentChangedAt":"2026-09-08T13:10:45.570Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}