{"record":{"id":"27ab62ca7993dff4","repo":"quarkusio/quarkus","slug":"cycle-detection-failure-report-dynamic-cyclebuild","errorCode":null,"errorMessage":"cycle detection failure report (dynamic CycleBuildException message listing dependency cycle)","messagePattern":"cycle detection failure report \\(dynamic CycleBuildException message listing dependency cycle\\)","errorType":"exception","errorClass":"ChainBuildException","httpStatus":null,"severity":"error","filePath":"core/builder/src/main/java/io/quarkus/builder/BuildChainBuilder.java","lineNumber":341,"sourceCode":"            if (!visited.add(builder)) {\n                final StringBuilder b = new StringBuilder(\"Cycle detected:\\n\\t\\t   \");\n                final Iterator<Produce> itr = producedPath.descendingIterator();\n                if (itr.hasNext()) {\n                    Produce produce = itr.next();\n                    for (;;) {\n                        b.append(produce.getStepBuilder().getBuildStep());\n                        ItemId itemId = produce.getItemId();\n                        b.append(\" produced \").append(itemId);\n                        b.append(\"\\n\\t\\tto \");\n                        if (!itr.hasNext())\n                            break;\n                        produce = itr.next();\n                        if (produce.getStepBuilder() == builder)\n                            break;\n                    }\n                    b.append(builder.getBuildStep());\n                }\n                throw new ChainBuildException(b.toString());\n            }\n            try {\n                final Set<Produce> dependencySet = dependencies.getOrDefault(builder, Collections.emptySet());\n                cycleCheckProduce(dependencySet, visited, checked, dependencies, producedPath);\n            } finally {\n                visited.remove(builder);\n            }\n        }\n        checked.add(builder);\n    }\n\n    private void addItem(final Map<ItemId, List<Produce>> allProduces, final Set<BuildStepBuilder> included,\n            final ArrayDeque<BuildStepBuilder> toAdd, final ItemId idToAdd) {\n        addItem(allProduces, included, toAdd, idToAdd, null);\n    }\n\n    private void addItem(final Map<ItemId, List<Produce>> allProduces, final Set<BuildStepBuilder> included,\n            final ArrayDeque<BuildStepBuilder> toAdd, final ItemId idToAdd, final Set<Produce> dependencies) {","sourceCodeStart":323,"sourceCodeEnd":359,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/core/builder/src/main/java/io/quarkus/builder/BuildChainBuilder.java#L323-L359","documentation":"Same check as doGet but in doPost of GreetingServlet: the servlet asserts an authenticated principal exists before echoing the POST body. If Elytron did not authenticate the request, getUserPrincipal() is null and getName() throws NPE, surfacing as this error. It indicates the POST bypassed or failed authentication.","triggerScenarios":"POST to /* without valid credentials or when the auth mechanism fails, so req.getUserPrincipal() is null and .getName() throws; or a null-name principal is installed.","commonSituations":"Test client forgetting to send basic-auth credentials on POST; security policy permitting POST anonymously; elytron realm not configured so no identities exist; session expired between GET and POST.","solutions":["Send valid authentication with the POST (basic auth header matching the elytron properties realm).","Confirm quarkus.http.auth.permission policies cover POST requests for the mapped path and a realm is configured.","Check @ServletSecurity constraints so container-managed auth runs before doPost.","Null-check getUserPrincipal() before invoking getName()."],"exampleFix":"// before\nif (req.getUserPrincipal().getName() == null) {\n    throw new RuntimeException(\"principal was null\");\n}\n// after\nif (req.getUserPrincipal() == null) {\n    throw new RuntimeException(\"principal was null\");\n}","handlingStrategy":"try-catch","validationCode":"boolean exists = given().header(\"tenantId\", tenant).get(\"/fruits/\" + id).getStatusCode() == 200;\nif (!exists) return; // nothing to delete","typeGuard":null,"tryCatchPattern":"Response r = given().header(\"tenantId\", tenant).delete(\"/fruits/\" + id);\nif (r.getStatusCode() == 404) {\n    // idempotent delete: already gone, acceptable\n} else {\n    r.then().statusCode(204);\n}","preventionTips":["Design deletes as idempotent on the client (404 == success)","Use ids captured in the same session, never stale cached ids","Double-check tenant header before delete in multi-tenant tests","Avoid double-delete in test cleanup; use @AfterEach with existence checks"],"tags":["servlet","security","elytron","authentication"],"backgroundTag":"unauthenticated-request-no-principal","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}