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(¶ms.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
- Await or cancel outstanding dynamic tool calls so their terminal receipts are appended and registry slots free up
- Cap the caller's own concurrency below 128 (e.g. a semaphore of ~64) so the runtime limit is never reached
- If calls appear leaked, complete or terminate their turns - settle_dynamic_tools_for_terminal_turn drains all calls for the turn
- 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
- Wrap registrations in a semaphore sized < 128
- Always pair every registration with a terminal outcome (result submit, timeout, or turn end) so entries drain
- Monitor the pending-call count if your runtime exposes it; saturation means a settlement leak
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
- Dynamic tool call '{}' is already pending
- Dynamic tool call '{call_id}' has an indeterminate terminal
- {error}
- Dynamic tool settlement task ended before acknowledgement
- Turn {turn_id} has an indeterminate dynamic-tool receipt; re
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/8be589f91505065a.
Report an issue: GitHub.