toeverything/AFFiNE · error · NetworkError

network_error

network_error

Error message

Captcha verification temporarily unavailable

What it means

Thrown when the outbound fetch to Cloudflare Turnstile's siteverify endpoint fails outright — DNS failure, TCP/TLS error, or the 5-second AbortSignal.timeout firing. The captcha service treats any inability to reach Turnstile as a temporary network condition, records the 'unavailable' metric, and raises NetworkError so callers can retry rather than blame the user.

Source

Thrown at packages/backend/server/src/plugins/captcha/service.ts:80

    formData.append('secret', this.captcha.turnstile.secret);
    formData.append('response', token);
    formData.append('remoteip', ip);
    formData.append('idempotency_key', randomUUID());

    const url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
    let result: Response;
    try {
      result = await fetch(url, {
        body: formData,
        method: 'POST',
        signal: AbortSignal.timeout(5000),
      });
    } catch {
      metrics.auth.counter('captcha_verification').add(1, {
        provider: 'turnstile',
        result: 'unavailable',
      });
      throw new NetworkError('Captcha verification temporarily unavailable');
    }
    if (!result.ok) {
      metrics.auth.counter('captcha_verification').add(1, {
        provider: 'turnstile',
        result: 'unavailable',
      });
      throw new NetworkError('Captcha verification temporarily unavailable');
    }
    let parsed: z.SafeParseReturnType<
      unknown,
      z.infer<typeof turnstileResponse>
    >;
    try {
      parsed = turnstileResponse.safeParse(await result.json());
    } catch {
      parsed = turnstileResponse.safeParse(null);
    }
    if (!parsed.success) {

View on GitHub (pinned to b4c8548c09)

Solutions

  1. Verify outbound HTTPS connectivity to challenges.cloudflare.com/turnstile/v0/siteverify from the server host (curl test).
  2. If a proxy is required, configure the standard proxy env vars / global agent so Node's fetch uses it.
  3. Fix container DNS (resolv.conf, CoreDNS) if the host resolves but the container does not.
  4. Retry the request after a short backoff — this error is explicitly 'temporarily unavailable'; if it persists, check Cloudflare status.

Example fix

null
Defensive patterns

Strategy: retry

Validate before calling

await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', { method: 'HEAD', signal: AbortSignal.timeout(3000) })
  .then(() => console.log('turnstile reachable'))
  .catch(() => console.error('turnstile unreachable — fix egress/DNS before enabling captcha'));

Type guard

const isNetworkError = (e: unknown): e is { code: 'network_error' } =>
  !!e && typeof e === 'object' && (e as any).code === 'network_error';

Try / catch

for (let attempt = 1; attempt <= 3; attempt++) {
  try {
    return await captchaService.verifyRequest(credential, req);
  } catch (e) {
    if (!isNetworkError(e) || attempt === 3) throw e;
    await backoff(attempt * 500);
  }
}

Prevention

When it happens

Trigger: Server has no outbound internet access (air-gapped or egress-firewalled self-host); Turnstile endpoint blocked by proxy rules; slow networks or Turnstile outages causing the 5s AbortSignal.timeout to abort; DNS resolution failures in the container.

Common situations: Self-hosted instances behind corporate proxies that whitelist only specific domains; Kubernetes pods with broken DNS; transient Cloudflare incidents; CI environments without network.

Related errors


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/55a3304acbe3bcdd. Report an issue: GitHub.