{"record":{"id":"7e350e0b0f9440b2","repo":"alibaba/spring-ai-alibaba","slug":"at-least-one-limit-must-be-specified-threadlimit-7e350e","errorCode":null,"errorMessage":"At least one limit must be specified (threadLimit or runLimit)","messagePattern":"At least one limit must be specified \\(threadLimit or runLimit\\)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/hook/toolcalllimit/ToolCallLimitHook.java","lineNumber":216,"sourceCode":"\n\t\tpublic Builder threadLimit(Integer threadLimit) {\n\t\t\tthis.threadLimit = threadLimit;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic Builder runLimit(Integer runLimit) {\n\t\t\tthis.runLimit = runLimit;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic Builder exitBehavior(ExitBehavior exitBehavior) {\n\t\t\tthis.exitBehavior = exitBehavior;\n\t\t\treturn this;\n\t\t}\n\n\t\tpublic ToolCallLimitHook build() {\n\t\t\tif (threadLimit == null && runLimit == null) {\n\t\t\t\tthrow new IllegalArgumentException(\"At least one limit must be specified (threadLimit or runLimit)\");\n\t\t\t}\n\t\t\treturn new ToolCallLimitHook(this);\n\t\t}\n\t}\n}\n","sourceCodeStart":198,"sourceCodeEnd":222,"githubUrl":"https://github.com/alibaba/spring-ai-alibaba/blob/f82da0b50f35744c13968191be2b1cd2452ef550/spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/hook/toolcalllimit/ToolCallLimitHook.java#L198-L222","documentation":"ToolCallLimitHook.Builder.build() requires at least one of threadLimit or runLimit to be set. A hook with no limits would never restrict anything, so the builder rejects it as a configuration mistake.","triggerScenarios":"Calling ToolCallLimitHook.builder().build() (or with only toolName/exitBehavior set) without ever calling .threadLimit(...) or .runLimit(...).","commonSituations":"Builder chain built dynamically where limit setters were skipped; copying a snippet and deleting the limit lines; assuming defaults exist (there are none).","solutions":["Set at least one limit: .threadLimit(n) and/or .runLimit(m) before build().","If you want only per-conversation caps use threadLimit; for per-run caps use runLimit; set both for combined enforcement.","Skip constructing the hook entirely when no limiting is desired, instead of building one with no limits."],"exampleFix":"// before\nToolCallLimitHook hook = ToolCallLimitHook.builder().build();\n// after\nToolCallLimitHook hook = ToolCallLimitHook.builder().threadLimit(10).build();","handlingStrategy":"validation","validationCode":"ToolCallLimitHook.Builder b = ToolCallLimitHook.builder();\n// ensure at least one limit is set before build\nif (threadLimit == null && runLimit == null) { throw new IllegalStateException(\"Set threadLimit or runLimit\"); }\nToolCallLimitHook hook = b.threadLimit(threadLimit).build();","typeGuard":"boolean hasLimit(Integer threadLimit, Integer runLimit) { return threadLimit != null || runLimit != null; }","tryCatchPattern":"try { hook = builder.build(); } catch (IllegalArgumentException e) { log.error(\"ToolCallLimitHook misconfigured: {}\", e.getMessage()); }","preventionTips":["Never build a limit hook without setting at least one limit.","Pick threadLimit for per-conversation caps, runLimit for per-run caps.","If no limiting is wanted, do not register the hook at all."],"tags":["java","builder-validation","configuration"],"backgroundTag":"missing-required-option","analyzedSha":"f82da0b50f35744c13968191be2b1cd2452ef550","analyzedAt":"2026-09-09T15:32:42.421Z","contentChangedAt":"2026-09-09T15:32:42.421Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}