{"record":{"id":"c46682135acedf99","repo":"elastic/elasticsearch","slug":"cannot-read-f-to-check-for-duplicate-license-he","errorCode":null,"errorMessage":"Cannot read ${f} to check for duplicate license headers","messagePattern":"Cannot read (.+?) to check for duplicate license headers","errorType":"exception","errorClass":"GradleException","httpStatus":null,"severity":"error","filePath":"build-conventions/src/main/java/org/elasticsearch/gradle/internal/conventions/precommit/LicenseHeadersTask.java","lineNumber":259,"sourceCode":"     * different headers is ambiguous about which license governs it, so we fail explicitly.\n     * <p>\n     * Detection strategy: count occurrences of the block-comment opener pattern\n     * {@code \"/*\\n * Copyright Elasticsearch B.V.\"} in each file (after normalising line endings to LF).\n     * Matching against the full opener rather than just the copyright string avoids false positives from\n     * files that legitimately mention the copyright text inside string literals, text blocks, or Javadoc\n     * comments (e.g. code-generators that embed a license header in the strings they emit).\n     */\n    private void checkForDuplicateHeaders(List<Problem> problems) {\n        // The exact prefix of every Elasticsearch license block-comment header.\n        final String HEADER_OPENER = \"/*\\n * Copyright Elasticsearch B.V.\";\n        for (FileCollection dirSet : getSourceFolders().get()) {\n            for (File f : dirSet.getAsFileTree().matching(patternFilterable -> patternFilterable.exclude(getExcludes()))) {\n                String content;\n                try {\n                    // Normalise Windows line endings so the pattern always contains a plain '\\n'.\n                    content = Files.readString(f.toPath(), StandardCharsets.UTF_8).replace(\"\\r\\n\", \"\\n\");\n                } catch (IOException e) {\n                    throw new GradleException(\"Cannot read \" + f + \" to check for duplicate license headers\", e);\n                }\n                int count = 0;\n                int idx = 0;\n                while ((idx = content.indexOf(HEADER_OPENER, idx)) != -1) {\n                    count++;\n                    idx += 1;\n                }\n                if (count > 1) {\n                    String path = f.getAbsolutePath();\n                    getLogger().error(\"Duplicate license header in: \" + path);\n                    problems.add(\n                        problemReporter.create(\n                            ProblemId.create(\n                                \"duplicate-license-header\",\n                                \"Duplicate license header\",\n                                ElasticsearchBuildProblems.LICENSE_HEADERS\n                            ),\n                            spec -> spec.contextualLabel(\"Duplicate license header in \" + path)","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/build-conventions/src/main/java/org/elasticsearch/gradle/internal/conventions/precommit/LicenseHeadersTask.java#L241-L277","documentation":"checkForDuplicateHeaders iterates every source file in the configured source folders and reads each as UTF-8 to count occurrences of the Elasticsearch license header opener. If Files.readString fails on a file (it does not exist as a readable file, encoding error, or permission denial) the IOException is wrapped as this GradleException. Note the file comes from a FileCollection tree, so Gradle already enumerated it — the failure is at read time.","triggerScenarios":"A file present in the source tree at enumeration time but unreadable when Files.readString is called: a broken symlink, a file deleted between Gradle's snapshot and the task body, a permissions change, or a non-UTF-8 byte sequence the reader rejects.","commonSituations":"A symlink in source folders pointing at a missing target; a stale Gradle cache referencing a file that was since removed; filesystem permissions tightened by a container user; a binary file misclassified as source.","solutions":["Open the path printed in the message and confirm it exists and is readable.","Remove or fix broken symlinks in the source folders (`find . -xtype l`).","Clean Gradle's input snapshots: `./gradlew --rerun-tasks` or clear the build cache.","Ensure the build user has read permission on the whole source tree."],"exampleFix":"// before: broken symlink in source tree\nln -s /nonexistent build-conventions/src/main/resources/missing.txt\n// after: remove the dangling link\nfind . -xtype l -delete","handlingStrategy":"validation","validationCode":"// Before running licenseHeaders, scan source folders for unreadable files\nfor (FileCollection fc : sourceFolders.get()) {\n    for (File f : fc.getAsFileTree()) {\n        if (!f.isFile() || !Files.isReadable(f.toPath())) {\n            throw new GradleException(\"Source file not readable: \" + f);\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    content = Files.readString(f.toPath(), StandardCharsets.UTF_8).replace(\"\\r\\n\", \"\\n\");\n} catch (IOException e) {\n    if (Files.isSymbolicLink(f.toPath()) && !Files.exists(f.toPath())) {\n        getLogger().warn(\"Skipping broken symlink {}\", f);\n        continue;\n    }\n    throw new GradleException(\"Cannot read \" + f + \" to check for duplicate license headers\", e);\n}","preventionTips":["Keep broken symlinks out of source trees (`find . -xtype l`).","Run the build as a user with read access to the whole tree.","Use `--rerun-tasks` if stale snapshots reference deleted files."],"tags":["gradle","license","io","precommit"],"backgroundTag":null,"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}