{"record":{"id":"a5573e0c02f2effd","repo":"apache/druid","slug":"unexpected-subdirectory-s","errorCode":null,"errorMessage":"Unexpected subdirectory [%s]","messagePattern":"Unexpected subdirectory \\[(.+?)\\]","errorType":"exception","errorClass":"IOException (IOE)","httpStatus":null,"severity":"error","filePath":"extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/S3DataSegmentPusher.java","lineNumber":138,"sourceCode":"        size += file.length();\n\n        try {\n          S3Utils.retryS3Operation(\n              () -> {\n                S3Utils.uploadFileIfPossible(s3Client, config.getDisableAcl(), config.getBucket(), s3Path + file.getName(), file);\n                return null;\n              }\n          );\n        }\n        catch (S3Exception e) {\n          throw handlePushServiceException(e, file.length());\n        }\n        catch (Exception e) {\n          throw new RuntimeException(e);\n        }\n      } else {\n        // Segment directories are expected to be flat.\n        throw new IOE(\"Unexpected subdirectory [%s]\", file.getName());\n      }\n    }\n\n    final int binaryVersion = SegmentUtils.getVersionFromDir(indexFilesDir);\n    // V10 unzipped is rangeable: a single druid.segment with a range-readable header. V9 unzipped is a directory of\n    // separate smoosh files the range-read path can't consume.\n    final boolean rangeable = binaryVersion == IndexIO.V10_VERSION;\n    return baseSegment.withSize(size)\n                      .withLoadSpec(makeLoadSpec(config.getBucket(), s3Path, rangeable))\n                      .withBinaryVersion(binaryVersion);\n  }\n\n  @Override\n  public Map<String, Object> makeLoadSpec(URI finalIndexZipFilePath)\n  {\n    // remove the leading \"/\"\n    return makeLoadSpec(finalIndexZipFilePath.getHost(), finalIndexZipFilePath.getPath().substring(1));\n  }","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/S3DataSegmentPusher.java#L120-L156","documentation":"Unzipped segment directories are expected to be flat: pushNoZip uploads each file in indexFilesDir but refuses subdirectories, since nested files cannot be mapped onto the flat S3 key layout for unzipped segments.","triggerScenarios":"Pushing an unzipped segment whose directory contains a subdirectory (e.g. old V9-style multi-smoosh layout or stray temp dirs inside the segment directory).","commonSituations":"Legacy V9 segments being re-pushed with the no-zip path; leftover hadoop job temp subdirectories inside the segment output; manually assembled segment dirs with nested structure.","solutions":["Flatten the segment directory so it contains only files.","Re-generate the segment with a current Druid version producing V10 (rangeable druid.segment) layout.","Remove stray temp/checkpoint subdirectories before pushing.","Zip the segment instead if the flat layout cannot be produced."],"exampleFix":"// before\nsegmentDir/\n  00000.smoosh\n  legacy/extra.smoosh   <- causes error\n// after\nsegmentDir/\n  00000.smoosh\n  00001.smoosh","handlingStrategy":"validation","validationCode":"File[] entries = dir.listFiles();\nfor (File f : entries) { if (f.isDirectory()) throw new IllegalStateException(\"nested dir: \" + f); }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep unzipped segment directories flat","Re-generate legacy V9 segments as V10 before re-pushing","Clean temp subdirectories out of segment output dirs"],"tags":["s3","push","segment-layout","validation"],"backgroundTag":"unsupported-source-layout","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}