{"record":{"id":"dc1c2c6f53f22039","repo":"bazelbuild/bazel","slug":"option-has-metadata-tag-s-but-does-not-have-categ","errorCode":null,"errorMessage":"Option has metadata tag %s but does not have category UNDOCUMENTED. Please fix.","messagePattern":"Option has metadata tag (.+?) but does not have category UNDOCUMENTED\\. Please fix\\.","errorType":"validation","errorClass":"OptionProcessorException","httpStatus":null,"severity":"error","filePath":"src/main/java/com/google/devtools/common/options/processor/OptionsClassProcessor.java","lineNumber":408,"sourceCode":"      if (tags.contains(OptionEffectTag.NO_OP)) {\n        throw new OptionProcessorException(\n            method,\n            \"Option includes NO_OP with other effects. This doesn't make much sense. Please \"\n                + \"remove NO_OP or the actual effects from the list, whichever is correct.\");\n      }\n    }\n  }\n\n  private void checkMetadataTagAndCategoryRationality(ExecutableElement method)\n      throws OptionProcessorException {\n    Option annotation = method.getAnnotation(Option.class);\n    OptionMetadataTag[] metadataTags = annotation.metadataTags();\n    OptionDocumentationCategory category = annotation.documentationCategory();\n\n    for (OptionMetadataTag tag : metadataTags) {\n      if (tag == OptionMetadataTag.HIDDEN || tag == OptionMetadataTag.INTERNAL) {\n        if (category != OptionDocumentationCategory.UNDOCUMENTED) {\n          throw new OptionProcessorException(\n              method,\n              \"Option has metadata tag %s but does not have category UNDOCUMENTED. Please fix.\",\n              tag);\n        }\n      }\n    }\n  }\n\n  private static final ImmutableSet<String> DEPRECATED_CATEGORIES =\n      ImmutableSet.of(\"undocumented\", \"hidden\", \"internal\");\n\n  private void checkOldCategoriesAreNotUsed(ExecutableElement method)\n      throws OptionProcessorException {\n    Option annotation = method.getAnnotation(Option.class);\n    if (DEPRECATED_CATEGORIES.contains(annotation.category())) {\n      throw new OptionProcessorException(\n          method,\n          \"Documentation level is no longer read from the option category. Category \\\"\"","sourceCodeStart":390,"sourceCodeEnd":426,"githubUrl":"https://github.com/bazelbuild/bazel/blob/e6e199d0601a244511b4cf18c8b2828aa73db1fd/src/main/java/com/google/devtools/common/options/processor/OptionsClassProcessor.java#L390-L426","documentation":"Thrown at compile time by Bazel's options annotation processor while validating the relationship between an option's metadataTags and its documentationCategory. Options marked with metadata tag HIDDEN or INTERNAL must use documentationCategory = UNDOCUMENTED, because hidden/internal options are by definition excluded from generated documentation.","triggerScenarios":"An @Option method with metadataTags containing OptionMetadataTag.HIDDEN or OptionMetadataTag.INTERNAL while documentationCategory is anything other than UNDOCUMENTED, e.g. documentationCategory = OptionDocumentationCategory.LOGGING with metadataTags = {OptionMetadataTag.INTERNAL}.","commonSituations":"Marking a previously-documented option as HIDDEN or INTERNAL but forgetting to flip its documentationCategory to UNDOCUMENTED; writing a new internal option by copying a documented one and only changing metadataTags; refactoring flags between categories during cleanup.","solutions":["Set documentationCategory = OptionDocumentationCategory.UNDOCUMENTED on the flagged option (this is the intended fix in essentially all cases).","Alternatively, if the option should stay documented, remove the HIDDEN/INTERNAL metadata tag that forced the mismatch.","Recompile; the check re-runs on every build of the options class."],"exampleFix":"// before\n@Option(\n  name = \"internal_cache_size\",\n  defaultValue = \"1024\",\n  documentationCategory = OptionDocumentationCategory.PERFORMANCE,\n  effectTags = {OptionEffectTag.EAGER},\n  metadataTags = {OptionMetadataTag.INTERNAL}\n)\n// after\n@Option(\n  name = \"internal_cache_size\",\n  defaultValue = \"1024\",\n  documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,\n  effectTags = {OptionEffectTag.EAGER},\n  metadataTags = {OptionMetadataTag.INTERNAL}\n)","handlingStrategy":"validation","validationCode":"static void checkCategoryMatchesMetadata(OptionDocumentationCategory category,\n                                        Set<OptionMetadataTag> metadataTags) {\n  if (metadataTags.contains(HIDDEN) || metadataTags.contains(INTERNAL)) {\n    Preconditions.checkState(category == OptionDocumentationCategory.UNDOCUMENTED,\n        \"HIDDEN/INTERNAL options must use UNDOCUMENTED category\");\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["When adding HIDDEN or INTERNAL to an option, always flip documentationCategory to UNDOCUMENTED in the same edit.","Write options with metadataTags first, then choose the category — the category follows from the tags."],"tags":["java","bazel","annotation-processing","options","compile-time","documentation","option-metadata-tags"],"backgroundTag":null,"analyzedSha":"e6e199d0601a244511b4cf18c8b2828aa73db1fd","analyzedAt":"2026-08-14T10:24:27.848Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}