ruvnet/ruflo · error

provider call failed

Error message

provider call failed

What it means

Thrown inside the JsModelProvider callback wired into a WASM agent: callAnthropicMessages() returned success=false, and when its error field was empty this generic message is used. The backing LLM HTTP call failed before producing output — auth, network, model, or quota — so the WASM agent's conversation turn cannot proceed.

Source

Thrown at v3/@claude-flow/cli/src/ruvector/agent-wasm.ts:172

 * Called once at agent-creation time; the provider stays attached for the
 * agent's lifetime.  No-op (returns false) when no provider keys are
 * configured so the echo-fallback path below is preserved for keyless
 * environments.
 */
async function attachJsModelProvider(agent: any, config: WasmAgentConfig): Promise<boolean> {
  const hasAny = !!(process.env.ANTHROPIC_API_KEY || process.env.OPENROUTER_API_KEY || process.env.OLLAMA_API_KEY);
  if (!hasAny) return false;
  const mod = await import('@ruvector/rvagent-wasm');
  const { callAnthropicMessages, resolveAnthropicModel } = await import('../mcp-tools/agent-execute-core.js');
  const model = resolveAnthropicModel(config.model);
  const systemPrompt = config.instructions || 'You are a helpful coding assistant running in a Ruflo WASM agent sandbox.';

  const provider = new mod.JsModelProvider(async (messagesJson: string) => {
    const messages: Array<{ role: string; content: string }> = JSON.parse(messagesJson);
    const lastUser = [...messages].reverse().find(m => m.role === 'user');
    const prompt = lastUser?.content ?? messagesJson;
    const result = await callAnthropicMessages({ prompt, systemPrompt, model, maxTokens: 2048 });
    if (!result.success) throw new Error(result.error ?? 'provider call failed');
    return JSON.stringify({ role: 'assistant', content: result.output ?? '' });
  });
  agent.set_model_provider(provider);
  return true;
}

/**
 * Send a prompt to a WASM agent.
 *
 * ADR-129 P1: JsModelProvider is now wired at creation time so the WASM
 * agent's internal conversation loop (multi-turn state, turn_count,
 * stop conditions) runs against a real LLM.  The echo-stub detection
 * block is kept as a fallback for keyless environments (CI, sandboxed
 * test runners) — behaviour is identical to the pre-P1 path when no
 * provider key is set.
 *
 * Billing note: every wasm_agent_prompt call with a provider key
 * configured makes a billable LLM call.  Use a keyless environment to

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Verify the key with a minimal authenticated request to the provider and expect 200/400 rather than 401
  2. Check the resolved model: log resolveAnthropicModel(config.model) and confirm it is a currently valid model id
  3. Retry after rate-limit windows and confirm remaining quota in the provider console
  4. Run keyless (unset ANTHROPIC_API_KEY / OPENROUTER_API_KEY / OLLAMA_API_KEY) when the echo stub is sufficient — it makes no provider calls

Example fix

# before
export ANTHROPIC_API_KEY=sk-ant-expired   # promptWasmAgent → 'provider call failed'
# after
export ANTHROPIC_API_KEY=<valid key>
# or, for cost-free sandboxing:
unset ANTHROPIC_API_KEY OPENROUTER_API_KEY OLLAMA_API_KEY   # echo stub
Defensive patterns

Strategy: fallback

Validate before calling

const hasKey = !!(process.env.ANTHROPIC_API_KEY || process.env.OPENROUTER_API_KEY || process.env.OLLAMA_API_KEY);
if (!hasKey) {
  // keyless path: agents run on the echo stub, no provider calls are made
  console.log('no provider key set — echo stub in effect');
}

Type guard

const isProviderCallFailure = (e: unknown): e is Error =>
  e instanceof Error && /provider call failed/.test(e.message);

Try / catch

try {
  return await promptWasmAgent(agentId, input);
} catch (e) {
  if (isProviderCallFailure(e)) {
    await ensureProviderReachable();  // key/network sanity check
    return `echo: ${input}`;          // fall back to keyless echo-stub behaviour
  }
  throw e;
}

Prevention

When it happens

Trigger: ANTHROPIC_API_KEY (or the resolved provider key) set but invalid/expired (401); rate limit or quota exhausted (429); network egress blocked from the sandbox; resolveAnthropicModel(config.model) producing a model id the API rejects.

Common situations: Rotated API keys with stale env vars in long-lived shells; CI runners without egress to the provider API; retired model names pinned in config; token quota exhausted mid-run.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/c15304e57754a571. Report an issue: GitHub.