{"record":{"id":"d5c82e9ece71f4ee","repo":"t8y2/dbx","slug":"agent-session-limit-reached-max-agent-sessions","errorCode":null,"errorMessage":"agent session limit reached: {MAX_AGENT_SESSIONS}","messagePattern":"agent session limit reached: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agents/drivers/tdengine/src/runtime.rs","lineNumber":239,"sourceCode":"            }\n        }\n    }\n\n    async fn open_session(&self, id: &str, params: ConnectParams) -> Result<()> {\n        if id.trim().is_empty() {\n            bail!(\"agentSessionId is required\");\n        }\n        {\n            let sessions = self.sessions.read().await;\n            if sessions.contains_key(id) {\n                bail!(\"agent session already exists: {id}\");\n            }\n        }\n        let slot = self\n            .session_slots\n            .clone()\n            .try_acquire_owned()\n            .map_err(|_| anyhow!(\"agent session limit reached: {MAX_AGENT_SESSIONS}\"))?;\n        let mut driver = TdengineDriver::new();\n        driver.connect(params).await?;\n        let session = Arc::new(AgentSession {\n            driver: Mutex::new(driver),\n            active: StdMutex::new(None),\n            next_operation_id: AtomicU64::new(1),\n            _slot: slot,\n        });\n        let mut sessions = self.sessions.write().await;\n        if sessions.contains_key(id) {\n            bail!(\"agent session already exists: {id}\");\n        }\n        sessions.insert(id.to_string(), session);\n        Ok(())\n    }\n\n    async fn session(&self, id: &str) -> Result<Arc<AgentSession>> {\n        self.sessions.read().await.get(id).cloned().ok_or_else(|| anyhow!(\"agent session not found: {id}\"))","sourceCodeStart":221,"sourceCodeEnd":257,"githubUrl":"https://github.com/t8y2/dbx/blob/c0390bff16418b651f4728520d99adf8ce48829a/agents/drivers/tdengine/src/runtime.rs#L221-L257","documentation":"open_session enforces a cap of MAX_AGENT_SESSIONS concurrently open sessions using an OwnedSemaphorePermit per session (the permit is stored in AgentSession::_slot and released when the session is closed/dropped). try_acquire_owned() fails when all permits are held, so the runtime refuses to open another session with this error. It is classified as a 'resource' error, retryable=true, session_disposition 'keep'.","triggerScenarios":"Calling open_session (or the legacy 'connect' method, which routes to open_session under LEGACY_SESSION_ID) when MAX_AGENT_SESSIONS sessions are already open and not closed; leaked sessions from prior connections that were never closed with close_session/disconnect.","commonSituations":"A client that opens a new agentSessionId per query without closing previous ones; app reconnect loops creating sessions after network blips while old sessions linger; a driver upgrade/restart of the host that lost track of session ids but left the agent process running; multi-connection tooling exceeding the configured session budget.","solutions":["Call close_session (or disconnect) for sessions no longer in use before opening new ones — permits are freed when sessions close","Reuse an existing session id: the error is retryable and sessions are kept, so retry after closing one, or reconnect to a live agentSessionId","Restart the agent process (or send 'shutdown') to clear all leaked sessions and free all slots","Audit the client for session leaks — every open_session must have a matching close_session, including on error paths"],"exampleFix":"// before: leak — new session per query, never closed\nfor q in queries {\n  send({\"method\":\"open_session\",\"params\":{\"agentSessionId\": format!(\"s-{}\", n), ...}});\n  run(q);\n}\n// after: close each session when done\nfor q in queries {\n  send(open_session(id));\n  run(q);\n  send({\"method\":\"close_session\",\"params\":{\"agentSessionId\": id}});\n}","handlingStrategy":"validation","validationCode":"const MAX_SESSIONS = 8; // must not exceed the agent's MAX_AGENT_SESSIONS\nconst openSessions = new Set();\nfunction canOpenSession(id) {\n  return !openSessions.has(id) && openSessions.size < MAX_SESSIONS;\n}\nif (!canOpenSession(id)) throw new Error(\"close an existing session before opening a new one\");","typeGuard":"function isSessionLimitError(res) {\n  return res?.error?.data?.category === \"resource\"\n    && /session limit reached/i.test(res.error?.message ?? \"\");\n}","tryCatchPattern":"try {\n  await sendRpc({ method: \"open_session\", params });\n} catch (res) {\n  if (isSessionLimitError(res)) {\n    const oldest = oldestOpenSession();\n    await sendRpc({ method: \"close_session\", params: { agentSessionId: oldest } });\n    await sendRpc({ method: \"open_session\", params }); // retryable=true, safe to retry\n  } else { throw res; }\n}","preventionTips":["Pair every open_session with a close_session/disconnect in a finally block, including on error paths","Track open agentSessionIds client-side and reuse sessions instead of opening new ones per query","Retry with backoff when the structured error reports retryable=true and session_disposition=keep","Send 'shutdown' or restart the agent when session state is suspected to be leaked after a client crash"],"tags":["capacity","sessions","resource-limit","tdengine","retryable"],"backgroundTag":"session-limit-reached","analyzedSha":"c0390bff16418b651f4728520d99adf8ce48829a","analyzedAt":"2026-09-05T23:05:10.900Z","contentChangedAt":"2026-09-05T23:05:10.900Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}