can1357/oh-my-pi · error · SearchProviderError

parallel: err.message (dynamic; thrown as…

Error message

parallel: err.message (dynamic; thrown as SearchProviderError("parallel", err.message, err.statusCode))

What it means

searchParallel catches a ParallelApiError from the Parallel web-search API, first offering it to a shared HTTP-error classifier (classifyProviderHttpError) and, if unclassified, re-wrapping the upstream message and status code in a SearchProviderError tagged with provider "parallel". This normalizes provider failures so callers of the aggregate search get one consistent error type regardless of which backend failed. The message is dynamic: it is whatever the Parallel API returned.

Solutions

  1. Check the err.statusCode and message to identify the upstream cause (auth vs rate limit vs server error)
  2. If auth-related, verify the Parallel API key is set and valid
  3. If 429, back off and retry after the rate-limit window
  4. Check the Parallel status page if statusCode >= 500

Example fix

// before
const r = await search({ provider: "parallel", query: q });
// after
try {
  const r = await search({ provider: "parallel", query: q });
} catch (err) {
  if (err instanceof SearchProviderError && err.provider === "parallel") {
    // inspect err.statusCode (401 -> fix key, 429 -> backoff)
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!process.env.PARALLEL_API_KEY) throw new Error('Parallel search requires PARALLEL_API_KEY');

Type guard

function isSearchProviderError(e: unknown): e is SearchProviderError {
  return e instanceof SearchProviderError;
}

Try / catch

try {
  const res = await search({ provider: "parallel", query });
} catch (err) {
  if (err instanceof SearchProviderError && err.provider === "parallel") {
    if (err.statusCode === 429) await backoffAndRetry();
    else if (err.statusCode === 401) fixCredentials();
    else fallbackProvider();
  } else throw err;
}

Prevention

When it happens

Trigger: Any HTTP error response from the Parallel Search API (searchParallel, invoked via search/result) whose status is not already mapped by classifyProviderHttpError — e.g. 401 invalid key, 429 rate limit, 5xx outage, or a malformed request the API rejects.

Common situations: Expired or missing PARALLEL_API_KEY, exceeded requests-per-minute quota, Parallel-side incidents, invalid query parameters (bad domain filter, oversized numResults).

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/a2b3bf1a484517f0. Report an issue: GitHub.

Appendix: source

Thrown at packages/coding-agent/src/web/search/providers/parallel.ts:171

				fetch: params.fetch,
			},
			authStorage,
			sessionId,
			sourcePolicy,
		);

		return {
			provider: "parallel",
			sources: toSearchSources(result.sources, numResults),
			requestId: result.requestId,
		};
	} catch (err) {
		if (err instanceof ParallelApiError) {
			if (typeof err.statusCode === "number") {
				const classified = classifyProviderHttpError("parallel", err.statusCode, err.message);
				if (classified) throw classified;
			}
			throw new SearchProviderError("parallel", err.message, err.statusCode);
		}
		throw err;
	}
}

export class ParallelProvider extends SearchProvider {
	readonly id = "parallel";
	readonly label = "Parallel";

	isAvailable(authStorage: AuthStorage) {
		return !!getEnvApiKey("parallel") || authStorage.hasAuth("parallel");
	}

	search(params: SearchParams): Promise<SearchResponse> {
		return searchParallel(
			{
				query: params.query,
				num_results: params.numSearchResults ?? params.limit,

View on GitHub (pinned to 9690622007)