Hmbown/CodeWhale · error

Runtime has reached the pending dynamic tool call limit ({MA

Error message

Runtime has reached the pending dynamic tool call limit ({MAX_PENDING_DYNAMIC_TOOL_CALLS})

What it means

register_pending_dynamic_tool rejects a new dynamic tool call because the pending registry already holds MAX_PENDING_DYNAMIC_TOOL_CALLS (128) entries. The registry only drains when a call's terminal receipt is durably appended, so this means 128 calls are concurrently awaiting results, timeouts, or turn-termination settlement.

Source

Thrown at crates/tui/src/runtime_threads.rs:3073

            .map(|(_, entry)| entry.request.clone())
            .collect::<Vec<_>>();
        user_inputs.sort_by(|left, right| {
            left.turn_id
                .cmp(&right.turn_id)
                .then_with(|| left.id.cmp(&right.id))
        });
        (approvals, user_inputs)
    }

    fn register_pending_dynamic_tool(
        &self,
        params: DynamicToolCallParams,
    ) -> Result<oneshot::Receiver<DynamicToolCallResult>> {
        let (tx, rx) = oneshot::channel();
        let (settlement_tx, _settlement_rx) = watch::channel(0);
        let mut pending = self.pending_dynamic_tools.lock();
        if pending.len() >= MAX_PENDING_DYNAMIC_TOOL_CALLS {
            bail!(
                "Runtime has reached the pending dynamic tool call limit ({MAX_PENDING_DYNAMIC_TOOL_CALLS})"
            );
        }
        if pending.contains_key(&params.call_id) {
            bail!("Dynamic tool call '{}' is already pending", params.call_id);
        }
        pending.insert(
            params.call_id.clone(),
            PendingDynamicToolEntry {
                params,
                sender: Some(tx),
                settlement_tx,
                indeterminate: false,
            },
        );
        Ok(rx)
    }

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Await or cancel outstanding dynamic tool calls so their terminal receipts are appended and registry slots free up
  2. Cap the caller's own concurrency below 128 (e.g. a semaphore of ~64) so the runtime limit is never reached
  3. If calls appear leaked, complete or terminate their turns - settle_dynamic_tools_for_terminal_turn drains all calls for the turn
  4. Report a bug if fewer than 128 live calls are visible; a settlement leak is draining slots

Example fix

// before
for task in tasks {
    let rx = manager.register_dynamic_tool_call(params(task)).await?; // can exceed 128
}

// after
let sem = Arc::new(tokio::sync::Semaphore::new(64));
for task in tasks {
    let permit = sem.clone().acquire_owned().await?;
    let rx = manager.register_dynamic_tool_call(params(task)).await?;
    tokio::spawn(async move {
        let _ = rx.await;
        drop(permit);
    });
}
Defensive patterns

Strategy: retry

Validate before calling

// Before registering, bound your own outstanding calls well under the limit.
const MAX_IN_FLIGHT: usize = 64; // runtime hard limit is 128
if outstanding_dynamic_calls.load(Ordering::SeqCst) >= MAX_IN_FLIGHT {
    return Err(anyhow::anyhow!("caller-side dynamic tool backpressure"));
}

Try / catch

// On the limit error: wait for settlements, then retry once.
match manager.register_dynamic_tool_call(params).await {
    Ok(rx) => Ok(rx),
    Err(e) if e.to_string().contains("pending dynamic tool call limit") => {
        wait_for_pending_settlements().await; // await outstanding oneshots/watch channels
        manager.register_dynamic_tool_call(params).await
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: A model turn issues more than 128 concurrent dynamic tool calls (register_dynamic_tool per call), or earlier calls were never settled because the caller never submitted results and no timeout fired. Checked at runtime_threads.rs:3072 before insertion.

Common situations: A fan-out agent loop that spawns tool calls without awaiting them; leaked calls from a turn whose completion path crashed; bursty workloads on a single runtime.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/8be589f91505065a. Report an issue: GitHub.