apple/pkl · error

commandOptionBothFlagAndArgument

commandOptionBothFlagAndArgument

Error message

commandOptionBothFlagAndArgument

What it means

collectOptions throws commandOptionBothFlagAndArgument when a single options-class property carries both a @Flag-style (boolean flag) annotation and an @Arg-style (positional argument) annotation. An option must be either a flag or a positional argument, never both; the property's header section identifies the conflict.

Source

Thrown at pkl-core/src/main/java/org/pkl/core/runtime/CommandSpecParser.java:217

      for (var prop : clazz.getDeclaredProperties()) {
        var name = prop.getName().toString();
        if (VmModifier.isLocalOrExternalOrAbstractOrFixedOrConst(prop.getModifiers())
            || opts.containsKey(name)) continue;

        VmTyped flagAnnotation = null;
        VmTyped argAnnotation = null;
        for (var annotation : prop.getAllAnnotations(true)) {
          if (annotation.getVmClass().isSubclassOf(CommandModule.getBaseFlagClass())) {
            if (flagAnnotation != null) continue;
            flagAnnotation = annotation;
          } else if (annotation.getVmClass() == CommandModule.getArgumentClass()) {
            if (argAnnotation != null) continue;
            argAnnotation = annotation;
          }
        }

        if (flagAnnotation != null && argAnnotation != null) {
          throw exceptionBuilder()
              .withSourceSection(prop.getHeaderSection())
              .evalError("commandOptionBothFlagAndArgument", prop.getName())
              .build();
        }

        if (argAnnotation != null) {
          var arg = collectArgument(prop, argAnnotation);
          opts.put(arg.name(), arg);
          if (arg.repeated()) {
            if (lastRepeatedArg == null) {
              lastRepeatedArg = arg;
            } else {
              throw exceptionBuilder()
                  .withSourceSection(optionsClass.getHeaderSection())
                  .evalError("commandArgumentsMultipleRepeated", lastRepeatedArg.name(), arg.name())
                  .build();
            }
          }

View on GitHub (pinned to f3efcbfc9b)

Solutions

  1. Remove one of the two annotations from the property
  2. Keep the @Argument annotation and drop the flag one if it should be positional
  3. Keep the flag annotation and drop @Argument if it should be an option-style flag

Example fix

// before
@BooleanFlag { name = "verbose" }
@Argument
verbose: Boolean
// after
@BooleanFlag { name = "verbose" }
verbose: Boolean
Defensive patterns

Strategy: validation

Validate before calling

// before shipping, assert each option property has at most one CLI annotation
const both = opts.filter(p => p.annotations.flag && p.annotations.arg);
if (both.length) throw new Error(`flag+argument on: ${both.map(p => p.name)}`);

Prevention

When it happens

Trigger: Annotating one property with both @BooleanFlag/@Flag/@CountedFlag and @Argument in the same options class.

Common situations: Copy-paste of annotation blocks where both annotation lines were left in; refactoring a flag into an argument without deleting the flag annotation.

Understand the failure class

Background: "mutually exclusive" flag errors: what "can't supply both nx and xx", "--raw is not compatible with -i" and "cannot be used with" mean, and how to fix them — this error's family across 29 libraries.

Related errors


AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08). Data as JSON: /api/errors/e91685343771e290. Report an issue: GitHub.