Render Tutorials
Stock research: from flaky to reliable

Why one failure stops the batch

⏱ 5 min

Reproduce

On your deployed app, submit four or five tickers. You may see:

  • One card error (fake “Exa rate limit”).
  • Other cards aborted even if they were running.
  • No synthesis phase.

That matches the baseline setup from page 1: parallel work in one process, and one rejection fails the entire batch.

maybeFail

Open tasks/src/search.ts:

tasks/src/search.ts
function maybeFail(query: string) {
if (Math.random() < 0.3) {
throw new Error(`Exa rate limit hit on query: "${query}"`)
}
}

searchOne calls maybeFail(spec.query) before the real Exa client (first argument is _topic, unused). The message looks like infrastructure. It is not.

Why one failure stops everything

In tasks/src/research.ts, each search runs inside Promise.all:

tasks/src/research.ts
try {
const result = await searchOne(query, spec, index)
onEvent({ type: 'search:done', index, articleCount: result.articles.length })
return result
} catch (err) {
onEvent({ type: 'search:failed', index, error: String(err) })
throw err
}

The UI can show search:failed on one card, but the callback rethrows. Promise.all rejects on the first rejection. runner.ts then emits { type: 'failed', error } for the whole run. Synthesis never runs.

Each search has ~70% chance to pass maybeFail. All four must pass: 0.7^4 ≈ 24% completion under this toy model.

What this means in production

If the same pattern reaches production with real transient dependency errors:

  • User requests fail in batches even when most sub-steps would have succeeded.
  • You waste cost and latency by restarting full runs instead of retrying only failed units.
  • Incident debugging gets noisy because one request mixes all concurrent failures in one process.

What you learned

  • maybeFail throws on ~30% of searches to simulate transient infrastructure errors
  • Per-card search:failed does not save the run: rethrow makes Promise.all fail the whole pipeline
  • All four searches must succeed for synthesis to run (~24% under this toy model)