chatboxai/chatbox · error · Error

Tool " " is not available

Error message

Tool "${part.toolName}" is not available

What it means

Thrown during single-call retry of a paused tool call (the retryToolCall path). buildToolsForPausedToolCall rebuilds the toolset; if part.toolName is not present or its entry has no execute function the retry cannot proceed. Same root cause as the batch path but for a one-shot retry.

Solutions

  1. Check tool availability before showing the retry button (rebuild tools lazily and test the name).
  2. On this error, mark the part as errored with a clear 'tool no longer available' message instead of a generic throw.
  3. Keep tool identifiers stable across releases.
  4. If agent mode was turned off, prompt the user to re-enable it before retrying action tools.

Example fix

// before
const toolValue = (tools as Record<string, unknown>)[part.toolName]
const executableTool = toolValue && typeof toolValue === 'object' ? (toolValue as ExecutableTool) : undefined
if (typeof executableTool?.execute !== 'function') {
  throw new Error(`Tool "${part.toolName}" is not available`)
}
// after
const toolValue = (tools as Record<string, unknown>)[part.toolName]
const executableTool = toolValue && typeof toolValue === 'object' ? (toolValue as ExecutableTool) : undefined
if (typeof executableTool?.execute !== 'function') {
  retryMessage = updateToolCallPart(retryMessage, toolCallId, (p) => ({ ...p, state: 'error', result: { error: `Tool "${part.toolName}" is no longer available` } }))
  await modifyMessage(sessionId, retryMessage, true)
  return
}
Defensive patterns

Strategy: validation

Validate before calling

const { tools } = await buildToolsForPausedToolCall(session, settings, retryMessage)
if (!isExecutableTool((tools as Record<string, unknown>)[part.toolName])) {
  // mark this part errored with a clear reason, do not throw
}

Type guard

function isExecutableTool(v: unknown): v is { execute: (...args: unknown[]) => Promise<unknown> } {
  return typeof v === 'object' && v !== null && typeof (v as { execute?: unknown }).execute === 'function'
}

Try / catch

try {
  await retryToolCall()
} catch (err) {
  if (err instanceof Error && /is not available$/.test(err.message)) {
    markPartErrored(retryMessage, toolCallId, err.message)
  } else throw err
}

Prevention

When it happens

Trigger: User retries a paused tool call after the tool became unavailable: settings changed (agent mode off, knowledge base removed, provider switched), tool renamed/removed in an update, or the model originally emitted a name not in the rebuilt set.

Common situations: Settings edited between pause and retry, plugin/tool unregistered by an update while a paused call waited, agent mode toggled off so the tool map no longer contains the action tool.

Related errors


AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12). Data as JSON: /api/errors/7127ec38528ff43b. Report an issue: GitHub.

Appendix: source

Thrown at src/renderer/stores/session/orchestration.ts:1451

    if (!settings) return

    let retryMessage = updateToolCallPart(retrySourceMessage, toolCallId, (toolPart) => ({
      ...toolPart,
      state: 'call',
      result: undefined,
      resultStorageKey: undefined,
      resultProviderMetadata: undefined,
      startTime: Date.now(),
      duration: undefined,
    }))
    await modifyMessage(sessionId, retryMessage, false)

    try {
      const { tools } = await buildToolsForPausedToolCall(session, settings, retryMessage)
      const toolValue = (tools as Record<string, unknown>)[part.toolName]
      const executableTool = toolValue && typeof toolValue === 'object' ? (toolValue as ExecutableTool) : undefined
      if (typeof executableTool?.execute !== 'function') {
        throw new Error(`Tool "${part.toolName}" is not available`)
      }

      const result = await executableTool.execute(part.args, {
        toolCallId,
        approved: true,
        approvalDetails: part.pauseReason?.type === 'app_action_approval' ? part.pauseReason.details : undefined,
      })
      retryMessage = updateToolCallPart(retryMessage, toolCallId, (toolPart) => ({
        ...toolPart,
        state: 'result',
        pauseReason: undefined,
        result,
        duration: toolPart.startTime ? Date.now() - toolPart.startTime : undefined,
      }))
      await modifyMessage(sessionId, retryMessage, true)

      await orchestrateGeneration(
        sessionId,

View on GitHub (pinned to 81571269ad)