halo-dev/halo · warning · Error

i18nResources.sendVerificationCodeFailed

Error message

i18nResources.sendVerificationCodeFailed

What it means

Terminal fallback in the email-code login error cascade. The fetch to /login/email-code/send returned !response.ok, but the parsed body has neither a non-empty `errors` array nor a `detail` field, so the code throws a localized generic message (i18nResources.sendVerificationCodeFailed). It is the catch-all for non-RFC-7807 responses — HTML error pages, empty bodies, gateway errors, or malformed JSON-rendered responses.

Source

Thrown at application/src/main/resources/templates/login_email-code.html:73

        const response = await fetch("/login/email-code/send", {
          method: "POST",
          body: JSON.stringify({ email: email }),
          headers: {
            "Content-Type": "application/json",
            [headerName]: token,
          },
        });

        if (!response.ok) {
          const json = await response.json();
          if (json.errors && json.errors.length) {
            throw new Error(json.errors[0]);
          }
          if (json.detail) {
            throw new Error(json.detail);
          }
          throw new Error(i18nResources.sendVerificationCodeFailed);
        }

        return response;
      }

      const emailCodeSendButton = document.getElementById("emailCodeSendButton");
      sendVerificationCode(emailCodeSendButton, sendRequest);
    });
  </script>
</div>

View on GitHub (pinned to d2f5165f9c)

Solutions

  1. Open DevTools Network and inspect both the status code and the raw response body for /login/email-code/send — a non-JSON/HTML body is the usual cause of this fallback.
  2. Confirm the request URL path matches the backend route registered in the current Halo version (route may have moved under /apis/...).
  3. If behind a proxy, check proxy error pages and ensure /login/* is passed through to the backend rather than served by the proxy.
  4. Add a temporary console.error(response.status, await response.text()) before the cascade to capture the true body during debugging.
  5. Verify the email-code login method is enabled in backend auth configuration; a disabled method can return an undocumented error body.

Example fix

// before
throw new Error(i18nResources.sendVerificationCodeFailed);
// after — preserve status code context for debugging while keeping the localized message
throw new Error(`${i18nResources.sendVerificationCodeFailed} (HTTP ${response.status})`);
Defensive patterns

Strategy: validation

Validate before calling

// Distinguish JSON ProblemDetail from non-JSON (HTML gateway) bodies before the cascade
async function parseProblemDetail(response) {
  const contentType = response.headers.get("content-type") || "";
  if (!contentType.includes("application/json") && !contentType.includes("application/problem+json")) {
    return { kind: "non-json", status: response.status };
  }
  let json;
  try { json = await response.json(); } catch { return { kind: "invalid-json", status: response.status }; }
  if (Array.isArray(json.errors) && json.errors.length) return { kind: "errors", json };
  if (typeof json.detail === "string") return { kind: "detail", json };
  return { kind: "unknown-json", status: response.status, json };
}

Type guard

function isJsonResponse(response: Response): boolean {
  const ct = response.headers.get("content-type") || "";
  return ct.includes("application/json") || ct.includes("application/problem+json");
}

Try / catch

// Separate the transport failure (non-ok, non-JSON) from the application error
if (!response.ok) {
  if (!isJsonResponse(response)) {
    // gateway/proxy HTML error — this is where fallback [142] fires
    throw new Error(`${i18nResources.sendVerificationCodeFailed} (HTTP ${response.status})`);
  }
  const json = await response.json();
  // ... handle errors[] / detail as above
}

Prevention

When it happens

Trigger: response.ok is false and the body is not a recognizable ProblemDetail: a 404 HTML page from a misrouted /login/email-code/send, a 502/504 from Nginx/Cloudflare returning HTML, response.json() parsed something unexpected, or the body is empty. The generic localized 'send verification code failed' message is shown.

Common situations: Reverse proxy (Nginx/Traefik) in front of Halo intercepts the request and returns its own HTML error; the endpoint URL changed in a version upgrade but the template still references the old path; response.json() succeeds on a non-error JSON shape (e.g. {status:'error'}) lacking both fields; deployment where login email-code feature/plugin is disabled returns a generic 4xx with an undocumented body.

Related errors


AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14). Data as JSON: /api/errors/d2d56334a44908a0. Report an issue: GitHub.