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

  1. Close the brace group: add the missing `}`
  2. Remove the stray `{` if no alternation was intended (it is otherwise emitted literally)
  3. 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

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)