The shape of the problem
A learner tells you they finished. The LMS shows incomplete, or complete with no score. Multiply by a few hundred learners and it becomes a compliance problem, a re-run of mandatory training, and a support queue. The frustrating part is that the course "works" — most people complete it fine — so the failure looks random. It is not. Lost completions come from a small number of specific mechanisms, each of which is preventable at build and detectable in testing. Here are the five that account for almost all of them.
Cause 1: the exit that never commits
SCORM only persists what the content asks it to persist, with Commit. If the course sets completion and then the window closes before a Commit reaches the LMS, the completion is gone. This happens when the content relies on the browser's unload event to commit — unload handlers are unreliable, fire late, and are cancelled outright on mobile — or when the learner closes the tab instead of clicking the course's own exit button.
Fix: commit as progress is made, not only at exit. A course that commits after each page and after the final status write has already persisted the completion before the learner reaches for the close button. In testing, close the tab abruptly (do not use the course's exit) right after finishing, then reopen and check the status held.
Cause 2: suspend data over the limit
SCORM 1.2 caps cmi.suspend_data at 4,096 characters. A course that stores the bookmark, every answer, and media progress in that string can exceed the cap on a long module. Many LMSs enforce the cap by truncating silently: the write appears to succeed, but half the state is gone. On resume, the course reads corrupted state, cannot tell it is finished, and reports incomplete.
Fix: keep suspend data lean, or build for SCORM 2004, which raises the cap to 64,000 characters. Either way, a conformance report that shows usage against the cap — "2.1 KB of 4 KB" — tells you whether you are close before a learner finds out. If you are near the limit in 1.2, that is a signal to move the course to 2004, as covered in SCORM 1.2 vs 2004.
Cause 3: status set in the wrong order
Some LMSs lock the attempt record the moment they first see completed or passed, and ignore anything written afterward. If the content sets completion before it sets the score, those platforms record the completion with an empty score — which many compliance rules treat as a fail, and which reads to the learner as "it didn't count".
Fix: write score first, then success, then completion, then commit. This ordering is invisible in the content and easy to get wrong in a converter, which is why it belongs on the conformance report as an explicit check. Detail in testing before upload.
Cause 4: the session that never initialised
If Initialize fails — because the content could not find the API object, or the LMS returned false — every later call is invalid and nothing is stored. The content often does not surface this; it just runs as if offline. The learner has a normal experience and the LMS records nothing.
API-not-found usually means the content is looking in the wrong place in the window hierarchy, which happens when an LMS launches courses in an unusual frame or a new window the content did not expect. A double Initialize — sometimes caused by a course being relaunched inside an already-open session — is rejected by strict platforms with the same result.
Fix: confirm in the runtime log that Initialize is called exactly once and returns true on the target platform's launch method. This is one of the behaviours a platform profile captures, because "how does this LMS launch the SCO" varies more than the specification admits.
Cause 5: commit throttling and dropped calls
To reduce server load, some LMSs throttle Commit — they honour one call every few seconds and quietly drop the rest. A course that commits rapidly near the end, expecting every call to land, can have its final completion commit dropped if it arrives within the throttle window of the previous one.
Fix: do not batch several commits into the last second of the session. Space the final writes, and make the completion commit the last thing that happens with a moment of headroom before Terminate. Replaying the session against a profile that models the platform's throttle reveals whether the final commit survives.
Prevention, in the build and the test
Every cause above is either a build decision or a test you can run before delivery:
- Commit as you go, not only on exit, and never depend on the unload event.
- Keep suspend data under the cap with headroom, or move to SCORM 2004.
- Write score before status; make the conformance report prove the order.
- Confirm a single successful
Initializeon the target platform's launch method. - Space the final commits; let the completion commit land before
Terminate. - Replay the finished session against the destination platform's profile and read the log.
Do these and the "random" losses stop being random and start being zero. The tools exist to make each check part of the build rather than a thing you remember to do — a validated manifest, a full runtime log per attempt, a suspend-data gauge, and saved platform profiles are exactly what SCORM Central produces on every build for this reason.