{"record":{"id":"64f67ec855503eda","repo":"sigoden/aichat","slug":"the-request-was-aborted-because-an-infinite-loop-o","errorCode":null,"errorMessage":"The request was aborted because an infinite loop of function calls was detected.","messagePattern":"The request was aborted because an infinite loop of function calls was detected\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/function.rs","lineNumber":28,"sourceCode":"use std::{\n    collections::{HashMap, HashSet},\n    fs,\n    path::{Path, PathBuf},\n};\n\n#[cfg(windows)]\nconst PATH_SEP: &str = \";\";\n#[cfg(not(windows))]\nconst PATH_SEP: &str = \":\";\n\npub fn eval_tool_calls(config: &GlobalConfig, mut calls: Vec<ToolCall>) -> Result<Vec<ToolResult>> {\n    let mut output = vec![];\n    if calls.is_empty() {\n        return Ok(output);\n    }\n    calls = ToolCall::dedup(calls);\n    if calls.is_empty() {\n        bail!(\"The request was aborted because an infinite loop of function calls was detected.\")\n    }\n    let mut is_all_null = true;\n    for call in calls {\n        let mut result = call.eval(config)?;\n        if result.is_null() {\n            result = json!(\"DONE\");\n        } else {\n            is_all_null = false;\n        }\n        output.push(ToolResult::new(call, result));\n    }\n    if is_all_null {\n        output = vec![];\n    }\n    Ok(output)\n}\n\n#[derive(Debug, Clone, Deserialize, Serialize)]","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/sigoden/aichat/blob/82976d349ad97ac9aae0655ad631dace5e2a6385/src/function.rs#L10-L46","documentation":"`eval_tool_calls` detects that after deduplicating the model's tool calls, no unique calls remain — the model keeps issuing the same call(s) repeatedly, which the library treats as an infinite function-call loop and aborts. This protects against runaway token spend and endless recursion.","triggerScenarios":"`ToolCall::dedup(calls)` returns an empty vector because all requested tool calls were duplicates of previously executed ones; raised from `call_chat_completions`/`call_chat_completions_streaming` agent loops.","commonSituations":"An LLM keeps re-invoking the same tool with identical arguments because the tool result doesn't satisfy it; a tool returns no useful output so the model retries forever; misconfigured tool schemas cause the model to loop.","solutions":["Make the tool return meaningful, distinct results so the model stops repeating the call","Improve the system prompt/tool descriptions so the model knows when to stop","Check tool command exit/output handling in `run_llm_function` (a failing tool can cause retries)","Cap loop iterations or dedup window in the agent config"],"exampleFix":"// before: tool always returns null\nOk(json!(null))\n// after\nOk(json!({\"status\": \"completed\", \"result\": output}))","handlingStrategy":"try-catch","validationCode":"if ToolCall::dedup(calls.clone()).is_empty() {\n    return Err(anyhow!(\"tool calls already executed; aborting loop\"));\n}","typeGuard":null,"tryCatchPattern":"match agent.run().await {\n    Err(e) if e.to_string().contains(\"infinite loop of function calls\") => {\n        eprintln!(\"Model looped on tool calls; refining prompt/tool output\");\n    }\n    other => other?,\n}","preventionTips":["Ensure tools return distinct, informative results","Write tool descriptions that tell the model when to stop","Add loop-iteration caps in your agent loop"],"tags":["llm","tool-calls","infinite-loop"],"backgroundTag":"internal-invariant-violation","analyzedSha":"82976d349ad97ac9aae0655ad631dace5e2a6385","analyzedAt":"2026-09-09T18:33:06.139Z","contentChangedAt":"2026-09-09T18:33:06.139Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}