{"record":{"id":"b3764e799d932478","repo":"bazelbuild/bazel","slug":"no-op-options-must-be-annotated-with-deprecated","errorCode":null,"errorMessage":"No-op options must be annotated with @Deprecated, or have metadata tag HIDDEN or INTERNAL. Alternatively add %s to the allowlist.","messagePattern":"No-op options must be annotated with @Deprecated, or have metadata tag HIDDEN or INTERNAL\\. Alternatively add (.+?) to the allowlist\\.","errorType":"validation","errorClass":"OptionProcessorException","httpStatus":null,"severity":"error","filePath":"src/main/java/com/google/devtools/common/options/processor/OptionsClassProcessor.java","lineNumber":518,"sourceCode":"          \"com.google.devtools.common.options.TestOptions\");\n\n  private void checkDeprecated(ExecutableElement method) throws OptionProcessorException {\n    Option annotation = method.getAnnotation(Option.class);\n    ImmutableList<OptionEffectTag> effectTags = ImmutableList.copyOf(annotation.effectTags());\n    ImmutableList<OptionMetadataTag> metadataTags = ImmutableList.copyOf(annotation.metadataTags());\n    boolean hasDeprecatedAnnotation = method.getAnnotation(Deprecated.class) != null;\n    boolean hasDeprecatedMetadataTag = metadataTags.contains(OptionMetadataTag.DEPRECATED);\n\n    if (effectTags.contains(OptionEffectTag.NO_OP)\n        && !metadataTags.contains(OptionMetadataTag.HIDDEN)\n        && !metadataTags.contains(OptionMetadataTag.INTERNAL)\n        && !hasDeprecatedAnnotation) {\n      // Allowlist for tests - these are in the process of being fixed.\n      String enclosingClassName = method.getEnclosingElement().toString();\n      boolean allowlisted =\n          NO_OP_OPTION_ALLOWLIST.stream().anyMatch(enclosingClassName::startsWith);\n      if (!allowlisted) {\n        throw new OptionProcessorException(\n            method,\n            \"No-op options must be annotated with @Deprecated, or have metadata tag HIDDEN or\"\n                + \" INTERNAL. Alternatively add %s to the allowlist.\",\n            enclosingClassName);\n      }\n    }\n\n    if (hasDeprecatedMetadataTag && !hasDeprecatedAnnotation) {\n      throw new OptionProcessorException(\n          method, \"Options with metadata tag DEPRECATED must be annotated with @Deprecated.\");\n    }\n    if (hasDeprecatedAnnotation && !hasDeprecatedMetadataTag) {\n      throw new OptionProcessorException(\n          method, \"Options annotated with @Deprecated must have metadata tag DEPRECATED.\");\n    }\n  }\n\n  private void checkConverter(ExecutableElement method) throws OptionProcessorException {","sourceCodeStart":500,"sourceCodeEnd":536,"githubUrl":"https://github.com/bazelbuild/bazel/blob/e6e199d0601a244511b4cf18c8b2828aa73db1fd/src/main/java/com/google/devtools/common/options/processor/OptionsClassProcessor.java#L500-L536","documentation":"This compile-time error enforces Bazel's policy that no-op options (effectTags contains NO_OP) must not look like live, supported flags. A NO_OP option must additionally be marked @Deprecated, or carry metadata tag HIDDEN or INTERNAL, or its enclosing class must be listed in the processor's temporary NO_OP_OPTION_ALLOWLIST (test classes being fixed). Otherwise the processor rejects it.","triggerScenarios":"An @Option method with effectTags = {OptionEffectTag.NO_OP} (alone or otherwise), whose class lacks @Deprecated, whose metadataTags contain neither HIDDEN nor INTERNAL, and whose fully-qualified enclosing class name does not start with one of the allowlist prefixes (e.g. com.google.devtools.build.lib.analysis.AnalysisCachingTest.).","commonSituations":"Introducing a new flag stubbed to do nothing while developing a feature; neutralizing a flag's implementation but leaving it publicly documented; upstream policy tightening (the allowlist is explicitly temporary, so previously-passing test classes can start failing when the allowlist shrinks).","solutions":["If the option is being retired, add @Deprecated to the method AND metadataTags = {OptionMetadataTag.DEPRECATED} (both are required by the paired checks).","If the option is intentionally hidden infrastructure, add OptionMetadataTag.HIDDEN or OptionMetadataTag.INTERNAL and set documentationCategory = UNDOCUMENTED.","If the option is supposed to actually do something, replace NO_OP with a real effect tag such as EAGER.","For test classes genuinely needing an unannotated no-op, add the fully-qualified enclosing class name plus trailing dot to NO_OP_OPTION_ALLOWLIST in OptionsClassProcessor.java — as a last resort, since the allowlist is scheduled for removal."],"exampleFix":"// before\n@Option(\n  name = \"stub_flag\",\n  defaultValue = \"false\",\n  documentationCategory = OptionDocumentationCategory.MISC,\n  effectTags = {OptionEffectTag.NO_OP}\n)\n// after\n@Option(\n  name = \"stub_flag\",\n  defaultValue = \"false\",\n  documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,\n  effectTags = {OptionEffectTag.NO_OP},\n  metadataTags = {OptionMetadataTag.DEPRECATED}\n)\n@Deprecated","handlingStrategy":"validation","validationCode":"static void checkNoOpIsMarked(boolean isNoOp,\n                              boolean hidden, boolean internal,\n                              boolean deprecated,\n                              String enclosingClass) {\n  if (isNoOp && !hidden && !internal && !deprecated) {\n    Preconditions.checkState(NO_OP_OPTION_ALLOWLIST.stream().anyMatch(enclosingClass::startsWith),\n        \"Unmarked no-op option in %s: add @Deprecated or HIDDEN/INTERNAL\", enclosingClass);\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["New no-op flags: add @Deprecated plus metadataTags DEPRECATED, or HIDDEN/INTERNAL with UNDOCUMENTED category, from day one.","Do not grow the allowlist for production code; it exists only for in-flight test fixes."],"tags":["java","bazel","annotation-processing","options","compile-time","no-op","deprecation","allowlist"],"backgroundTag":null,"analyzedSha":"e6e199d0601a244511b4cf18c8b2828aa73db1fd","analyzedAt":"2026-08-14T10:24:27.848Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}