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
- 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.
- Confirm the request URL path matches the backend route registered in the current Halo version (route may have moved under /apis/...).
- If behind a proxy, check proxy error pages and ensure /login/* is passed through to the backend rather than served by the proxy.
- Add a temporary console.error(response.status, await response.text()) before the cascade to capture the true body during debugging.
- 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
- Check response.headers content-type before response.json() to avoid masking HTML errors as 'unexpected token'.
- Keep the login template in sync with the backend route path across version upgrades.
- Ensure reverse proxies pass /login/* through to Halo instead of serving their own error pages.
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
- json.errors[0]
- problemDetail.comment.systemUsersOnly
- json.detail
- No ESM host runtime snapshot is available for Halo ${targetH
- Cannot resolve shared dependency ${root} from ${resolutionBa
AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14).
Data as JSON: /api/errors/d2d56334a44908a0.
Report an issue: GitHub.