paperclipai/paperclip · error
ACPX pending tool limit reached
Error message
ACPX pending tool limit reached
What it means
Resource-cap guard in the ACPX runtime sidecar's waitForTool: the number of tool calls pending within the current turn exceeded the sidecar's bounded pending-tool limit, so further calls are rejected rather than growing an unbounded waiter map. Fires when a single provider turn fans out more concurrent tool calls than the configured cap — a backstop against runaway or malformed provider turns.
Solutions
- Reduce the number of concurrent tool calls the agent issues per turn (serialize or batch them).
- If the workload legitimately needs more parallel calls, raise the pending-tool limit in the sidecar configuration.
- Check for a provider bug that leaks pending entries (calls that never settle) and drains the budget.
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at packages/paperclip-runner/src/cli/acpx-runtime-sidecar.ts:593 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/0ad9a5510c12bb8d.
Report an issue: GitHub.
Appendix: source
Thrown at packages/paperclip-runner/src/cli/acpx-runtime-sidecar.ts:593
} finally {
rejectTurnWaiters(currentTurnId, "ACPX turn became terminal");
if (turnId === currentTurnId) turnId = null;
}
// A terminal frame is also runnerd's permission to recycle this provider.
// Publish it only after no callback can inherit this turn's mutable binding.
emit("runtime.turn_terminal", terminal, currentTurnId);
}
async function waitForTool(call: RunnerToolCall): Promise<unknown> {
const activeTurnId = turnId;
if (!activeTurnId || call.signal.aborted) {
throw new Error("ACPX tool call is not bound to an active turn");
}
const callId = boundedIdentity(call.callId, "callId");
if (tools.has(callId)) throw new Error("ACPX tool call is duplicated");
const operationId = boundedIdentity(call.tool, "operationId");
if (tools.size >= MAX_PENDING_TOOLS) {
throw new Error("ACPX pending tool limit reached");
}
if (
operationId === PRP_COMPLETION_TOOL_NAME ||
operationId === PRP_BLOCK_TOOL_NAME
) {
const validation = validatePrpStructuredRunResult(call.arguments);
if (!validation.ok) {
throw new Error("ACPX semantic result failed PRP schema validation");
}
const blocked = validation.result.reportedWorkDisposition === "blocked";
if (
(operationId === PRP_BLOCK_TOOL_NAME && !blocked) ||
(operationId === PRP_COMPLETION_TOOL_NAME && blocked)
) {
throw new Error(
"ACPX semantic result disposition does not match its terminal operation",
);
}View on GitHub (pinned to 3f1d897a7c)