{"record":{"id":"51495e1005ad1806","repo":"alibaba/spring-ai-alibaba","slug":"recursionlimit-must-be-0","errorCode":null,"errorMessage":"recursionLimit must be > 0!","messagePattern":"recursionLimit must be > 0!","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/CompileConfig.java","lineNumber":182,"sourceCode":"\t/**\n\t * Builder class for creating instances of CompileConfig. It allows setting various\n\t * options such as savers, interrupts, and lifecycle listeners in a fluent manner.\n\t */\n\tpublic static class Builder {\n\n\t\tprivate final CompileConfig config;\n\n\t\t/**\n\t\t * Initializes the builder with the provided compile configuration.\n\t\t * @param config The base configuration to start from.\n\t\t */\n\t\tprotected Builder(CompileConfig config) {\n\t\t\tthis.config = new CompileConfig(config);\n\t\t}\n\n\t\tpublic Builder recursionLimit(int recursionLimit) {\n\t\t\tif( recursionLimit <= 0 ) {\n\t\t\t\tthrow new IllegalArgumentException(\"recursionLimit must be > 0!\");\n\t\t\t}\n\t\t\tthis.config.recursionLimit = recursionLimit;\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * Sets whether the thread should be released during execution.\n\t\t * @param releaseThread Flag indicating whether to release the thread.\n\t\t * @see BaseCheckpointSaver#release(RunnableConfig)\n\t\t * @return This builder instance for method chaining.\n\t\t */\n\t\tpublic Builder releaseThread(boolean releaseThread) {\n\t\t\tthis.config.releaseThread = releaseThread;\n\t\t\treturn this;\n\t\t}\n\n\t\t/**\n\t\t * Sets the observation registry for monitoring and tracing.","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/CompileConfig.java#L164-L200","documentation":"CompileConfig.Builder.recursionLimit enforces a strictly positive recursion limit because graph execution uses it to cap the number of node transitions and prevent infinite loops. Passing 0 or a negative value throws IllegalArgumentException.","triggerScenarios":"Calling new CompileConfig.Builder().recursionLimit(0) or recursionLimit(negative), often from a config value that defaulted to 0 or was computed as -1.","commonSituations":"Loading recursion limit from application properties that were unset (0); arithmetic producing a non-positive value; copying a config object whose limit was never initialized.","solutions":["Pass a positive integer (e.g. 25–50 for typical agent loops).","Default the config value: int limit = configured > 0 ? configured : 25;","Prefer CompileConfig.recursionLimit() over the deprecated CompiledGraph.setMaxIterations."],"exampleFix":"// before\nCompileConfig cfg = CompileConfig.builder().recursionLimit(0).build();\n// after\nCompileConfig cfg = CompileConfig.builder().recursionLimit(25).build();","handlingStrategy":"validation","validationCode":"if (configuredLimit <= 0) configuredLimit = 25; CompileConfig cfg = CompileConfig.builder().recursionLimit(configuredLimit).build();","typeGuard":"Integer safeRecursionLimit(Integer v) { return (v == null || v <= 0) ? 25 : v; }","tryCatchPattern":"try { cfg = CompileConfig.builder().recursionLimit(n).build(); } catch (IllegalArgumentException e) { cfg = CompileConfig.builder().recursionLimit(25).build(); }","preventionTips":["Never pass raw config values; clamp/normalize before calling recursionLimit.","Default unset properties to a positive constant.","Use CompileConfig.recursionLimit instead of the deprecated setMaxIterations."],"tags":["configuration","graph-compile","validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"f82da0b50f35744c13968191be2b1cd2452ef550","analyzedAt":"2026-09-09T15:32:42.421Z","contentChangedAt":"2026-09-09T15:32:42.421Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}