Freika/dawarich · warning
[Onboarding] Failed to persist completion:
Error message
[Onboarding] Failed to persist completion:
What it means
The onboarding modal fires a PATCH to onboardingUrlValue to record that the user finished onboarding, guarded only by .catch. fetch only rejects on network-level failures (offline, DNS, aborted request), so HTTP 4xx/5xx statuses pass silently and do NOT produce this message. When it does fire, the analytics event has already been tracked but the server still considers onboarding unfinished, so the modal can reappear next visit.
Source
Thrown at app/javascript/controllers/onboarding_modal_controller.js:199
screen !== targetName,
)
}
}
}
completeOnboarding() {
this.trackEvent("onboarding_completed")
if (this.onboardingUrlValue) {
fetch(this.onboardingUrlValue, {
method: "PATCH",
headers: {
"X-CSRF-Token": document.querySelector('meta[name="csrf-token"]')
?.content,
"Content-Type": "application/json",
},
}).catch((error) => {
console.warn("[Onboarding] Failed to persist completion:", error)
})
}
}
trackEvent(eventName) {
if (typeof window.sa_event === "function") {
window.sa_event(eventName)
}
}
}
View on GitHub (pinned to 97fad417c5)
Solutions
- Check the Network tab: a PATCH showing a 4xx/5xx status did NOT trigger this catch — handle response.ok separately
- Ensure the layout renders csrf_meta_tags so the X-CSRF-Token header is valid
- Add an explicit response.ok check and a single retry so transient failures still persist completion
- If the modal keeps reappearing, confirm the PATCH reached the server in Rails logs
Example fix
// before
fetch(this.onboardingUrlValue, { method: "PATCH", headers })
.catch((error) => {
console.warn("[Onboarding] Failed to persist completion:", error)
})
// after
try {
const res = await fetch(this.onboardingUrlValue, { method: "PATCH", headers, keepalive: true })
if (!res.ok) console.warn("[Onboarding] Persist returned HTTP", res.status)
} catch (error) {
console.warn("[Onboarding] Failed to persist completion:", error)
fetch(this.onboardingUrlValue, { method: "PATCH", headers, keepalive: true }).catch(() => {}) // one retry
} Defensive patterns
Strategy: retry
Validate before calling
const token = document.querySelector('meta[name="csrf-token"]')?.content
if (!this.onboardingUrlValue || !token) {
console.warn("[Onboarding] Missing URL or CSRF token; skipping persist")
return
} Try / catch
try {
const res = await fetch(this.onboardingUrlValue, { method: "PATCH", headers, keepalive: true })
if (!res.ok) throw new Error(`HTTP ${res.status}`)
} catch (error) {
console.warn("[Onboarding] Failed to persist completion:", error)
fetch(this.onboardingUrlValue, { method: "PATCH", headers, keepalive: true }).catch(() => {})
} Prevention
- Always pair fetch .catch with an explicit response.ok check — fetch only rejects on network failure
- Use keepalive: true for fire-and-forget persistence that must survive page navigation
- Verify csrf_meta_tags is present in every layout that renders the modal
When it happens
Trigger: User goes offline or the tab suspends as the modal closes; server or DNS unreachable; the request aborted by Turbo navigation replacing the page; a missing csrf-token meta tag yields a 422 that is a silent failure, not this catch; onboardingUrlValue pointing at a nonexistent route.
Common situations: Mobile Safari suspending the tab on modal dismissal; Rails layout skipping csrf_meta_tags so the X-CSRF-Token header is blank; reverse proxy 502 during deploys; staging URLs left in a copied template.
Related errors
- [Settings] Failed to check recalculation status:
- errorData.error || `Failed to ${isEdit ? "update" : "create"
- Failed to fetch points: ${response.statusText}
- Failed to create visit
- Failed to load poster theme "${key}" (${response.status})
AI-assisted analysis of Freika/dawarich@97fad417c5 (2026-08-21).
Data as JSON: /api/errors/6b424d2da3253033.
Report an issue: GitHub.