paperclipai/paperclip · error · ToolGatewayHttpError

err.reasonCode

err.reasonCode

Error message

err.message

What it means

During slot acquisition for a gateway tool call, a ToolRuntimeSupervisorError thrown by the runtime supervisor is converted into a ToolGatewayHttpError preserving its HTTP status, message, reasonCode, and details. This surfaces supervisor-level slot failures (e.g. capacity limits, stuck slots, restart storms) as structured HTTP errors to the caller.

Solutions

  1. Inspect err.reasonCode and err.status to identify the supervisor failure (capacity vs startup vs stuck slot).
  2. Stop unused slots or wait for idleTtlMs (1s default) / restart backoff to elapse, then retry the tool call.
  3. Raise maxCompanySlots/maxHostSlots in ToolRuntimeSupervisorOptions if limits are legitimately too low.
  4. Check the tool access audit events (toolAccessAuditEvents) for the restart/backoff history of the slot.

Example fix

// before
await executeGatewayTool(input); // throws ToolGatewayHttpError (supervisor)
// after
try {
  await executeGatewayTool(input);
} catch (e) {
  if (e instanceof ToolGatewayHttpError && e.reasonCode === "slot_capacity") {
    await stopIdleSlots(companyId);
    return executeGatewayTool(input); // retry after capacity freed
  }
  throw e;
}
Defensive patterns

Strategy: retry

Validate before calling

const slots = await getCompanySlots(companyId);
if (slots.filter(s => ["starting","running","idle"].includes(s.status)).length >= 4) await stopIdleSlots(companyId);

Type guard

function isSupervisorGatewayError(e: unknown): e is ToolGatewayHttpError {
  return e instanceof ToolGatewayHttpError && typeof e.reasonCode === "string" && e.reasonCode.startsWith("slot");
}

Try / catch

try {
  return await executeGatewayTool(input);
} catch (e) {
  if (isSupervisorGatewayError(e) && [429, 503].includes(e.status)) {
    await backoffRetry(() => executeGatewayTool(input), { attempts: 3, baseMs: 1000 });
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling executeGatewayTool when the supervisor cannot allocate/restart the runtime slot: company slot limit (maxCompanySlots) reached, host slot limit reached, restart storm detected, slot stuck beyond stuckSlotMs, or the runtime process failed to start.

Common situations: A company runs more than maxCompanySlots (4) concurrent tool connections; the runtime binary crashes repeatedly triggering backoff/storm protection; an idle slot expired between check and use; deployment mode/exposure misconfiguration.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/286525c3505c9e1d. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/tool-gateway.ts:10890

    async listRuntimeSlots(companyId?: string) {
      return runtimeSupervisor.listSlots(companyId);
    },

    async stopRuntimeSlot(input: {
      companyId: string;
      slotId: string;
      actor?: { agentId?: string | null; runId?: string | null };
    }) {
      try {
        return await runtimeSupervisor.stopSlot({
          companyId: input.companyId,
          slotId: input.slotId,
          agentId: input.actor?.agentId ?? null,
          runId: input.actor?.runId ?? null,
        });
      } catch (err) {
        if (err instanceof ToolRuntimeSupervisorError) {
          throw new ToolGatewayHttpError(
            err.status,
            err.message,
            err.reasonCode,
            err.details,
          );
        }
        throw err;
      }
    },

    async restartRuntimeSlot(input: {
      companyId: string;
      slotId: string;
      actor?: { agentId?: string | null; runId?: string | null };
    }) {
      try {
        return await runtimeSupervisor.restartSlot({
          companyId: input.companyId,

View on GitHub (pinned to 3f1d897a7c)