{"record":{"id":"c14fe6c936ef20e0","repo":"arduino/Arduino","slug":"invalid-archive-it-must-contain-a-single-root-fol","errorCode":null,"errorMessage":"Invalid archive: it must contain a single root folder","messagePattern":"Invalid archive: it must contain a single root folder","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"arduino-core/src/cc/arduino/utils/ArchiveExtractor.java","lineNumber":170,"sourceCode":"          continue;\n        }\n\n        if (entry instanceof TarArchiveEntry) {\n          TarArchiveEntry tarEntry = (TarArchiveEntry) entry;\n          mode = tarEntry.getMode();\n          isLink = tarEntry.isLink();\n          isSymLink = tarEntry.isSymbolicLink();\n          linkName = tarEntry.getLinkName();\n        }\n\n        // On the first archive entry, if requested, detect the common path\n        // prefix to be stripped from filenames\n        if (stripPath > 0 && pathPrefix.isEmpty()) {\n          int slash = 0;\n          while (stripPath > 0) {\n            slash = name.indexOf(\"/\", slash);\n            if (slash == -1) {\n              throw new IOException(\"Invalid archive: it must contain a single root folder\");\n            }\n            slash++;\n            stripPath--;\n          }\n          pathPrefix = name.substring(0, slash);\n        }\n\n        // Strip the common path prefix when requested\n        if (!name.startsWith(pathPrefix)) {\n          throw new IOException(\"Invalid archive: it must contain a single root folder while file \" + name + \" is outside \" + pathPrefix);\n        }\n        name = name.substring(pathPrefix.length());\n        if (name.isEmpty()) {\n          continue;\n        }\n        File outputFile = new File(destFolder, name);\n\n        File outputLinkedFile = null;","sourceCodeStart":152,"sourceCodeEnd":188,"githubUrl":"https://github.com/arduino/Arduino/blob/a0df6e0e83b652c72bc78b0a1376c54d6ebc3bee/arduino-core/src/cc/arduino/utils/ArchiveExtractor.java#L152-L188","documentation":"When extract() is asked to strip a root folder (stripPath > 0), it computes the common path prefix from the first entry's name by skipping stripPath '/' characters. If the entry name contains fewer slashes than requested, it throws this IOException, because the archive does not have the single root folder the caller expects to strip.","triggerScenarios":"extract(file, dest, stripPath=N) with N=1 (typical) but the first archive entry's path has no '/' — e.g. the archive contains files at its root or a single file instead of a top-level directory.","commonSituations":"Library released as a flat archive (files at archive root, no wrapping folder); archive whose first entry is './name' or a bare filename; someone re-zips library contents without the standard 'LibraryName/' root directory.","solutions":["Repackage the archive so all files sit under a single top-level folder named after the library (e.g. MyLibrary/src/...)","Call extract with stripPath=0 if the archive legitimately has no root folder and you want files extracted as-is","Inspect the archive (tar -tf) to confirm the directory layout before extracting"],"exampleFix":"// before\narchiveExtractor.extract(archiveFile, destFolder, 1); // archive is flat\n// after (repack with root folder) OR:\narchiveExtractor.extract(archiveFile, destFolder, 0); // no strip requested","handlingStrategy":"validation","validationCode":"// inspect first entry layout before choosing stripPath\ntry (TarArchiveInputStream in = new TarArchiveInputStream(new FileInputStream(archiveFile))) {\n  TarArchiveEntry e = in.getNextTarEntry();\n  if (e != null && e.getName().indexOf('/') == -1) throw new IllegalArgumentException(\"Archive has no root folder; use stripPath=0 or repackage\");\n}","typeGuard":null,"tryCatchPattern":"try { extractor.extract(archive, dest, 1); } catch (IOException e) { if (e.getMessage().contains(\"single root folder\")) { extractor.extract(archive, dest, 0); } else { throw e; } }","preventionTips":["Package libraries under a single top-level folder named after the library","Verify with `tar -tf lib.tar.gz | head -1` that entries start with 'LibName/'","Avoid flat archives when the installer strips a path prefix"],"tags":["archive","structure","io","arduino"],"backgroundTag":"invalid-argument-value","analyzedSha":"a0df6e0e83b652c72bc78b0a1376c54d6ebc3bee","analyzedAt":"2026-09-06T10:13:38.901Z","contentChangedAt":"2026-09-06T10:13:38.901Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}