apple/pkl · error · InvalidGlobPatternException
invalidGlobUnclosedSubpattern
invalidGlobUnclosedSubpattern
Error message
invalidGlobUnclosedSubpattern
What it means
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.
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
Example fix
// before
glob("{src,test/**.pkl")
// after
glob("{src,test}/**.pkl") Defensive patterns
Strategy: validation
Validate before calling
function bracesBalanced(g) {
let depth = 0;
for (const c of g) {
if (c === '{') depth++;
if (c === '}') depth--;
if (depth < 0) return false;
}
return depth === 0;
} Type guard
const isClosedGlob = (p) => [...p].filter(c => c === '{').length === [...p].filter(c => c === '}').length; Try / catch
try {
var regex = GlobResolver.toRegexPattern(glob);
} catch (InvalidGlobPatternException e) {
if (e.getMessage().contains("UnclosedSubpattern")) throw new IllegalArgumentException("Unclosed { in glob: " + glob);
throw e;
} Prevention
- Count braces when building patterns programmatically
- Lint globs for balanced { } before use
- Avoid interpolating untrusted strings into brace groups
When it happens
Trigger: A glob with an unterminated `{`, e.g. "{a,b/ or "foo{{.pkl", passed to toRegexPattern/toRegexString via glob import or resource globbing.
Common situations: Typos when hand-writing brace expansions, programmatically built patterns where a variable holding the closing brace was empty, or truncation of the pattern string.
Related errors
AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08).
Data as JSON: /api/errors/612dd82aea97d729.
Report an issue: GitHub.
Appendix: source
Thrown at pkl-core/src/main/java/org/pkl/core/util/GlobResolver.java:237
throw new InvalidGlobPatternException(ErrorMessages.create("invalidGlobExtGlob"));
}
sb.append("\\+");
}
case '!' -> {
var next = getNextChar(globPattern, i);
if (next == '(') {
throw new InvalidGlobPatternException(ErrorMessages.create("invalidGlobExtGlob"));
}
sb.append("!");
}
// no special meaning in glob patterns but have special meaning in regex.
case '.', '(', '%', '^', '$', '|' -> sb.append("\\").append(current);
default -> sb.append(current);
}
}
if (inGroup) {
throw new InvalidGlobPatternException("invalidGlobUnclosedSubpattern");
}
return sb.append("$").toString();
}
private static void resolveOpaqueGlob(
SecurityManager securityManager,
ReaderBase reader,
URI globUri,
Pattern pattern,
Map<String, ResolvedGlobElement> result)
throws IOException, SecurityManagerException, ExternalReaderProcessException {
var elements = reader.listElements(securityManager, globUri);
for (var elem : sorted(elements)) {
URI resolvedUri;
try {
resolvedUri = new URI(globUri.getScheme(), elem.getName(), null);
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e.getMessage(), e);View on GitHub (pinned to f3efcbfc9b)