{"record":{"id":"78cfffe987a5b906","repo":"google-gemini/gemini-cli","slug":"max-turns-exceeded-78cfff","errorCode":"MAX_TURNS_EXCEEDED","errorMessage":"MAX_TURNS_EXCEEDED","messagePattern":"MAX_TURNS_EXCEEDED","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/core/src/agent/legacy-agent-session.ts","lineNumber":187,"sourceCode":"    } finally {\n      this._clearActiveStream();\n    }\n  }\n\n  private async _runLoop(\n    initialParts: Part[],\n    initialDisplayContent?: string,\n  ): Promise<void> {\n    let currentParts: Part[] = initialParts;\n    let currentDisplayContent = initialDisplayContent;\n    let turnCount = 0;\n    const maxTurns = this._config.getMaxSessionTurns();\n\n    while (true) {\n      turnCount++;\n      if (maxTurns >= 0 && turnCount > maxTurns) {\n        this._finishStream('max_turns', {\n          code: 'MAX_TURNS_EXCEEDED',\n          maxTurns,\n          turnCount: turnCount - 1,\n        });\n        return;\n      }\n\n      const toolCallRequests: ToolCallRequestInfo[] = [];\n      let finishedReason: FinishReason | undefined = undefined;\n      const responseStream = this._client.sendMessageStream(\n        currentParts,\n        this._abortController.signal,\n        this._promptId,\n        undefined,\n        currentDisplayContent,\n      );\n      currentDisplayContent = undefined;\n\n      for await (const event of responseStream) {","sourceCodeStart":169,"sourceCodeEnd":205,"githubUrl":"https://github.com/google-gemini/gemini-cli/blob/5024443c7217464a66e98f80d73172a26440bd8f/packages/core/src/agent/legacy-agent-session.ts#L169-L205","documentation":"MAX_TURNS_EXCEEDED here is the local (client-side) enforcement in the legacy agent loop. _runLoop increments turnCount each iteration and, when it exceeds config.getMaxSessionTurns() (and maxTurns>=0), calls _finishStream('max_turns', { code:'MAX_TURNS_EXCEEDED', maxTurns, turnCount }) and returns. This is the producer of the event that [242] translates.","triggerScenarios":"Each while(true) iteration in _runLoop: turnCount++; if (maxTurns>=0 && turnCount>maxTurns) -> _finishStream('max_turns', {...}); return. Triggered when a session runs longer than the configured budget.","commonSituations":"Default maxTurns is too low for the task; a tool returns errors so the loop never finishes; misconfigured getMaxSessionTurns returning a small number.","solutions":["Increase config.getMaxSessionTurns() / setMaxSessionTurns().","Set maxTurns to -1 to disable the client-side cap (let server policy govern).","Inspect tool_request/tool_response stream to find why turns accumulate and fix the failing tool."],"exampleFix":"// before\ngetMaxSessionTurns() { return 20; }\n// after\ngetMaxSessionTurns() { return 100; }","handlingStrategy":"validation","validationCode":"const max = config.getMaxSessionTurns();\nif (max >= 0 && max < taskEstimatedTurns) config.setMaxSessionTurns(taskEstimatedTurns);","typeGuard":"function isMaxTurnsFinish(payload: unknown): payload is { code: 'MAX_TURNS_EXCEEDED'; maxTurns: number; turnCount: number } {\n  return typeof payload === 'object' && payload !== null && (payload as any).code === 'MAX_TURNS_EXCEEDED';\n}","tryCatchPattern":"// _finishStream is internal; callers react to the resulting agent_end event:\nif (ev.type === 'agent_end' && ev.reason === 'max_turns' && isMaxTurnsFinish(ev.data)) { /* raise budget or refine task */ }","preventionTips":["Set maxTurns proportional to expected tool-call depth.","Use maxTurns = -1 only when server-side policy is the real cap."],"tags":["agent-loop","turn-budget","legacy-session","typescript"],"backgroundTag":null,"analyzedSha":"5024443c7217464a66e98f80d73172a26440bd8f","analyzedAt":"2026-08-12T06:01:53.711Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}