{"record":{"id":"be99c43d10188f15","repo":"abi/screenshot-to-code","slug":"agent-exceeded-max-tool-turns","errorCode":null,"errorMessage":"Agent exceeded max tool turns","messagePattern":"Agent exceeded max tool turns","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"backend/agent/engine.py","lineNumber":327,"sourceCode":"                            len(tool_result.updated_content), \"tool_result\"\n                        )\n\n                await self._send(\n                    \"toolResult\",\n                    data={\n                        \"name\": tool_call.name,\n                        \"output\": tool_result.summary,\n                        \"ok\": tool_result.ok,\n                    },\n                    event_id=tool_event_id,\n                )\n                executed_tool_calls.append(\n                    ExecutedToolCall(tool_call=tool_call, result=tool_result)\n                )\n\n            await session.append_tool_results(turn, executed_tool_calls)\n\n        raise Exception(\"Agent exceeded max tool turns\")\n\n    async def run(self, model: Llm, prompt_messages: List[ChatCompletionMessageParam]) -> str:\n        self.tool_runtime.input_images = self._extract_input_images(prompt_messages)\n        seed_file_state_from_messages(self.file_state, prompt_messages)\n\n        if self.recorder is not None:\n            self.recorder.record_run_start(model, prompt_messages)\n\n        session = create_provider_session(\n            model=model,\n            prompt_messages=prompt_messages,\n            should_generate_images=self.should_generate_images,\n            openai_api_key=self.openai_api_key,\n            openai_base_url=self.openai_base_url,\n            anthropic_api_key=self.anthropic_api_key,\n            gemini_api_key=self.gemini_api_key,\n            replicate_api_key=self.replicate_api_key,\n            # Only advertise extraction when the request actually contains a","sourceCodeStart":309,"sourceCodeEnd":345,"githubUrl":"https://github.com/abi/screenshot-to-code/blob/d026163f586dfa8c5c10d28c36edd59a9d3b0e88/backend/agent/engine.py#L309-L345","documentation":"Generic Exception raised at the end of _run_with_session when the agent exhausts its hardcoded 30-step tool loop (for _ in range(max_steps) with max_steps = 30) without the model ever producing a tool-call-free final turn. Each iteration is one model turn; the loop only exits early when turn.tool_calls is empty (final answer).","triggerScenarios":"The model keeps requesting tool calls (editing files, generating images, reading state) for 30 consecutive turns and never emits a plain text final response.","commonSituations":"Under-powered models stuck in a tool loop, prompts that never tell the model when to stop, or a task too large for 30 turns (big multi-file generation with image assets).","solutions":["Use a stronger model that converges in fewer turns.","Tighten the user prompt so the model knows to output final code and stop calling tools.","Raise max_steps in backend/agent/engine.py:219 if the workload legitimately needs more turns.","Check the run recording (recorder logs every turn) to see which tool the model loops on and fix that tool's result so the model can proceed."],"exampleFix":"# before (backend/agent/engine.py)\nmax_steps = 30\n\n# after\nmax_steps = 50  # still bounded, but allows larger tasks","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    html = await engine.run(model, messages)\nexcept Exception as e:\n    if \"exceeded max tool turns\" in str(e):\n        # retry with stronger model or simplified prompt\n        ...","preventionTips":["Keep generation prompts explicit about producing final code without further tool calls.","Prefer models that converge; test new model integrations with small screenshots first.","If you control the engine, make max_steps configurable rather than hardcoded so callers can tune it."],"tags":["agent","loop-limit","timeout","engine"],"backgroundTag":null,"analyzedSha":"d026163f586dfa8c5c10d28c36edd59a9d3b0e88","analyzedAt":"2026-08-14T22:02:06.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}