astrid-runtime/astrid · error

MCP gateway attach limit reached for principal '{principal}'

Error message

MCP gateway attach limit reached for principal '{principal}' ({MAX_ATTACHES})

What it means

The MCP gateway caps each principal at MAX_ATTACHES = 16 live attach sessions. In `acquire`, after trying a plain semaphore permit and then evicting the least-recently-used idle slot, if no permit can be obtained the gateway rejects the attach. This is an intentional process-local resource cap so a hostile host cannot make one principal consume an unbounded number of broker sessions.

Source

Thrown at crates/astrid-cli/src/commands/mcp/gateway.rs:219

        self.permits
            .lock()
            .await
            .entry(principal.to_owned())
            .or_insert_with(|| Arc::new(Semaphore::new(MAX_ATTACHES)))
            .clone()
    }

    async fn acquire(&self, principal: &str) -> Result<OwnedSemaphorePermit> {
        let semaphore = self.semaphore_for(principal).await;
        if let Ok(permit) = semaphore.clone().try_acquire_owned() {
            return Ok(permit);
        }
        if self.evict_lru_idle().await
            && let Ok(permit) = semaphore.try_acquire_owned()
        {
            return Ok(permit);
        }
        anyhow::bail!(
            "MCP gateway attach limit reached for principal '{principal}' ({MAX_ATTACHES})"
        )
    }

    /// Reserve a host session through replacement, cap admission, and slot
    /// installation as one linearizable operation. The reservation owns the
    /// admission guard until `install` publishes the new slot.
    async fn reserve_session(
        &self,
        host_session_id: &str,
        principal: &str,
    ) -> Result<AttachReservation> {
        let admission = Arc::clone(&self.admission).lock_owned().await;
        if self.shutdown.is_cancelled() {
            anyhow::bail!("MCP gateway is shutting down");
        }
        self.replace_session(host_session_id).await?;
        let permit = self.acquire(principal).await?;

View on GitHub (pinned to affd8760f4)

Solutions

  1. Reduce the number of concurrent `mcp attach` sessions for this principal to at most 16; close or disconnect idle/stale attach processes.
  2. Check for orphaned gateway attach processes (ps / lsof on the Unix socket) and kill stale ones so their slots free up.
  3. Retry the attach after existing sessions go idle or EOF; idle slots are evicted LRU automatically.
  4. If 16 is genuinely too small for your workload, this cap is a compile-time constant (MAX_ATTACHES) and requires a code change, not configuration.

Example fix

// before: many parallel attach clients each opening their own session
for i in 0..32 { spawn(mcp_attach(principal)); }
// after: reuse one attach session per host-session id, or close idle ones
for i in 0..32 { spawn(mcp_attach(principal)).join().unwrap(); } // or pool <= 16 sessions
Defensive patterns

Strategy: validation

Validate before calling

fn can_attach(live_sessions: usize, all_active: bool) -> bool {
    live_sessions < 16 || !all_active // a slot must be free or LRU-evictable
}

Try / catch

match gateway.reserve_session(host_session_id, principal).await {
    Err(e) if e.to_string().contains("attach limit reached") => queue_attach_for_retry(),
    other => other?,
}

Prevention

When it happens

Trigger: Calling reserve_session (via acquire) when the principal already holds 16 live attach slots and none of them are idle enough to be LRU-evicted; also hit when a replacement attach cannot be admitted within REPLACEMENT_TIMEOUT because the predecessor has not released its slot.

Common situations: Opening more than 16 concurrent MCP attach sessions for the same principal (e.g. many editor windows or agent processes each running `mcp attach`); leaked/stale sessions that are still considered active so LRU eviction cannot free a slot; reconnect storms during flaky network conditions.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/aa468c9e86814bb4. Report an issue: GitHub.