{"record":{"id":"d5221d2649b70126","repo":"xai-org/grok-build","slug":"max-turns-reached","errorCode":null,"errorMessage":"max turns reached","messagePattern":"max turns reached","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codegen/xai-grok-pager/src/headless.rs","lineNumber":1304,"sourceCode":"            {\n                Some(r) => r,\n                None => {\n                    tracing::warn!(\n                        \"headless: prompt response carried no requestId; emitting an empty requestId\"\n                    );\n                    \"\"\n                }\n            };\n            let is_max_turns = resp\n                .meta\n                .as_ref()\n                .and_then(|m| m.get(crate::app::CANCELLATION_CATEGORY_KEY))\n                .and_then(|v| v.as_str())\n                == Some(xai_grok_shell::session::commands::MAX_TURNS_REACHED_CATEGORY);\n            if is_max_turns {\n                emitter.on_max_turns();\n                emitter.on_end(&stop_reason, sid, rid);\n                Err(anyhow::anyhow!(\"max turns reached\"))\n            } else {\n                emitter.on_end(&stop_reason, sid, rid);\n                Ok(())\n            }\n        }\n        Some(Err(err)) => {\n            let msg = if i32::from(err.code) == RATE_LIMITED_ERROR_CODE {\n                let detail = err.data.as_ref().and_then(error_detail_from_data);\n                crate::app::sanitize_user_error(&format_rate_limited_user_message(\n                    detail.as_deref(),\n                    is_api_key_auth,\n                ))\n            } else {\n                err.to_string()\n            };\n            if let Some(usage) = xai_grok_shell::sampling::error::prompt_usage_from_error(&err) {\n                match serde_json::to_value(&usage) {\n                    Ok(v) => emitter.usage = Some(v),","sourceCodeStart":1286,"sourceCodeEnd":1322,"githubUrl":"https://github.com/xai-org/grok-build/blob/bc7f02eddd3d84085849dc19ed216f11c23b0571/crates/codegen/xai-grok-pager/src/headless.rs#L1286-L1322","documentation":"run_single_turn returns anyhow!(\"max turns reached\") when the session ended with the MAX_TURNS_REACHED_CATEGORY stop reason: the agent consumed options.max_turns turns without finishing, so the run is treated as a failure after emitting on_max_turns and on_end.","triggerScenarios":"Setting --max-turns (options.max_turns) too low for the task; the agent loops on a failing tool call or repeatedly requests permission and burns turns; an interactive-ish task executed headlessly.","commonSituations":"CI runs with a small default max_turns on complex prompts; retry loops where the model keeps re-issuing the same failing command; permission prompts auto-denied headlessly consuming every turn.","solutions":["Increase --max-turns to fit the task","Simplify/narrow the prompt so fewer turns are needed","Fix the underlying tool failure causing the turn loop","Check session metadata for the category key to distinguish max-turns from other stops"],"exampleFix":"// before\npager --max-turns 3 -p \"refactor the whole module\"\n// after\npager --max-turns 25 -p \"refactor the whole module\"","handlingStrategy":"fallback","validationCode":"let estimated_turns = count_tool_calls_needed(prompt);\nlet max_turns = std::env::var(\"MAX_TURNS\").ok()\n    .and_then(|v| v.parse::<u32>().ok())\n    .unwrap_or_else(|| estimated_turns.max(10));","typeGuard":"fn is_max_turns_reason(metadata: &serde_json::Value) -> bool {\n    metadata.get(\"category\").and_then(|c| c.as_str())\n        == Some(\"max_turns_reached\")\n}","tryCatchPattern":"match run_single_turn(&mut session, &options).await {\n    Err(e) if e.to_string() == \"max turns reached\" => {\n        eprintln!(\"agent hit turn budget; increasing and retrying\");\n        options.max_turns *= 2;\n        run_single_turn(&mut session, &options).await?;\n    }\n    other => other?,\n}","preventionTips":["Size max_turns to the task complexity, not the default","Detect repeated identical tool calls to break loops early","Monitor the max-turns metadata category in headless runs","Keep prompts focused so fewer turns are consumed"],"tags":["agent","limit","headless","loop"],"backgroundTag":"max-turns-exceeded","analyzedSha":"bc7f02eddd3d84085849dc19ed216f11c23b0571","analyzedAt":"2026-08-31T04:59:42.031Z","schemaVersion":2},"datasetVersion":"2026-08-31T09:17:48.483Z"}