ruvnet/ruflo · error · Error
${result.error ?? 'provider call failed'}
Error message
${result.error ?? 'provider call failed'} What it means
Thrown inside the JsModelProvider callback wired to a WasmAgent: the v3 provider call (callAnthropicMessages) returned { success: false }, so the callback throws to signal failure to the WASM runtime. The message is result.error when present, else the literal 'provider call failed'. Because the provider is attached at agent-creation time only when ANTHROPIC_API_KEY / OPENROUTER_API_KEY / OLLAMA_API_KEY is set, this only fires in a keyed environment — every prompt is a real billable call.
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 toView on GitHub (pinned to 6b01dc5a68)
Solutions
- Inspect the full result.error (the message interpolates it) — a 401 means rotate the key, 429 means back off, 'model not found' means fix the model string.
- Verify the env var the process actually sees: console.log(Boolean(process.env.ANTHROPIC_API_KEY)) — dotenv may not have loaded in this entrypoint.
- For transient failures (429, 5xx), wrap the prompt call in a retry with exponential backoff; the WASM runtime does not retry on its own.
- For local/offline runs, unset all three provider keys so the echo-stub fallback engages instead of failing through the provider path.
Example fix
// before — provider error surfaces as a thrown Error mid-prompt
const out = await promptWasmAgent(agentId, input);
// after — catch provider failures and fall back gracefully
try {
const out = await promptWasmAgent(agentId, input);
} catch (e) {
if (/provider call failed|rate.?limit|unauthorized|401|429/i.test(String(e))) {
// surface a user-actionable message instead of crashing the agent loop
throw new Error(`LLM provider unreachable: ${e}. Check ANTHROPIC_API_KEY and provider status.`);
}
throw e;
} Defensive patterns
Strategy: retry
Validate before calling
function hasProviderKey(): boolean {
return !!(process.env.ANTHROPIC_API_KEY || process.env.OPENROUTER_API_KEY || process.env.OLLAMA_API_KEY);
}
// If you don't want billable calls (or provider failures), run keyless to get the echo stub.
// If you do, preflight the key with a cheap call:
async function preflightProvider(): Promise<boolean> {
const { callAnthropicMessages } = await import('../mcp-tools/agent-execute-core.js');
const r = await callAnthropicMessages({ prompt: 'ping', systemPrompt: '', model: resolveAnthropicModel(undefined), maxTokens: 1 });
return r.success;
} Try / catch
async function promptWithRetry(agentId: string, input: string, retries = 2): Promise<string> {
for (let attempt = 0; ; attempt++) {
try {
return await promptWasmAgent(agentId, input);
} catch (e) {
const msg = String(e);
const transient = /429|rate.?limit|timeout|econnreset|5\d\d/.test(msg);
if (!transient || attempt >= retries) throw e;
await new Promise(r => setTimeout(r, 500 * 2 ** attempt));
}
}
} Prevention
- Confirm the env var is actually set in the process (print Boolean(process.env.ANTHROPIC_API_KEY)).
- Validate model strings against the provider's current catalog before wiring the agent.
- For offline/CI runs, unset all three keys so the echo stub engages instead of failing through the provider path.
- Wrap multi-turn agent loops with retry+backoff for 429/5xx; the WASM runtime does not retry on its own.
When it happens
Trigger: API key is set but invalid/expired (401); rate limit hit (429); requested model not available on the configured provider; RUFLO_PROVIDER points at a provider whose endpoint is unreachable; OpenRouter routing fails for an uncommon model; Ollama key set but ollama daemon not running; network proxy blocks the provider endpoint.
Common situations: ANTHROPIC_API_KEY rotated in the dashboard but the process still holds the old value; free-tier OpenRouter key hit its daily cap; model string like 'anthropic:claude-sonnet-4-6' resolved to a model the account doesn't have access to; running in CI behind a corporate firewall that blocks api.anthropic.com; transient provider outage during a long multi-turn agent run.
Related errors
- SSRF guard: private/loopback host rejected — ${host}
- SSRF guard: private/loopback host rejected — ${host}
- Failed to create share link
- Unauthorized
- MCP server "${server.name}" is in cooldown (HTTP ${cd.status
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/a673bde4be58c49b.
Report an issue: GitHub.