an OS that ships a speech synthesiser enumerates at least one voice
Check id speech-voices-vs-platform
What an ordinary browser yields
On a Windows or macOS claim, at least one voice after the voiceschanged event has had its chance (severity: warn). Any other platform declines.
What a detector infers
The plain speech-voices readout above refuses to score an empty list, and as written it is right to: Chrome does not build the voice list in the renderer. The browser process owns it and hands it over by IPC, so the first synchronous getVoices() on a cold document returns an empty array and a voiceschanged event follows once the list lands. A check that reads once and blames the browser for that array is measuring its own timing rather than the browser. But the excuse is removable — wait for the event, with a budget, and only then decide. Once the browser has had its chance to deliver a list and has not, not-loaded-yet explains nothing. The platform gate is then the whole check. An empty voice list is perfectly ordinary on Linux without speech-dispatcher, which is exactly why the readout cannot score it globally; it is not ordinary on Windows or macOS, both of which ship a speech synthesiser as part of the operating system rather than as an optional component, and have done for their entire modern history. That is durable OS knowledge rather than a version table, so there is no per-release list here that can go stale. One measured detail makes or breaks the implementation, and it is worth stating plainly because the obvious version of this check gets it wrong. The FIRST voiceschanged event carries an empty list: on this machine the event fires at about 233ms with getVoices() still returning zero, and again at about 256ms with the real six. So the natural implementation — wait for voiceschanged, then read — settles on the empty list and concludes the machine has no speech engine at all. That is precisely the false reading it produced during development, on a browser that in fact enumerates the host's real SAPI set (Microsoft George, Hazel and Susan for en-GB, David, Mark and Zira for en-US) about 786ms into the page. An event therefore ends the wait only when it brings a non-empty list; an empty one is ignored and the budget keeps running, so the only way to reach a zero verdict is to run out of time with nothing ever delivered. Stock Chrome 150 on the same machine enumerates 25 voices, six of them local. This row finds nothing on either browser tested here, and is kept because the emptiness case is real and unscoreable in the plain readout — not because anything was caught with it.
How to resolve it
Nothing to fix if this really is a stripped image — Windows N, Server core and some VDI templates do ship without voices, which is why this reports implausible rather than impossible. If the OS being claimed is an ordinary desktop, though, the empty list is a property of the browser and not of the machine: the voice list crosses from the browser process by IPC, so a build that never wires that path enumerates nothing while a stock browser on the very same machine enumerates the host's real synthesiser. Leave the speech pipeline connected.
Nearby checks in Environment & locale
- AudioContext invariants + a deterministic offline render
audio-context-coherenceThe check constructs a live AudioContext and reads four invariants — sampleRate, baseLatency, the destination node's channelCount against… - the analyser byte spectrum is the defined quantisation of its float spectrum
audio-analyser-float-vs-byteAn AnalyserNode publishes the same spectrum twice. getFloatFrequencyData returns decibels, and getByteFrequencyData returns that spectrum… - speech synthesis voices (host OS speech engine)
speech-voicesspeechSynthesis.getVoices() enumerates the host operating system's speech engine — a resource stealth stacks essentially never touch, which… - the voice list arrives the way Chrome delivers it
speech-voices-async-pathspeech-voices reads WHAT is in the voice list. This reads HOW the list arrived, which is a separate fact and survives a roster whose every… - a live audio destination corresponds to an audio device that exists
audio-sink-vs-device-listA resource oracle, and deliberately a different resource from the voice list. - WebSQL (openDatabase) is absent for Chrome ≥ 119
opendatabase-removedWebSQL was removed from Chrome in version 119, so window.openDatabase should not exist on any newer Chromium. - BatteryManager fields satisfy the spec's mutual-exclusion invariants
battery-spec-invariantsThe probe calls navigator.getBattery() once and records four fields together: level, charging, chargingTime, dischargingTime. - enumerateDevices exposes audiooutput on a Chromium claim
media-kinds-vs-engineThe probe calls navigator.mediaDevices.enumerateDevices() and keeps only each entry's .kind, sorted — never labels or deviceIds, which is…
Clearcote is a browser built for fingerprint coherence
It is a Chromium fork, maintained by the same people who wrote this reference. It ships as a compiled browser rather than as a stealth script injected into someone else's — which is a description of how it is built, and is not an argument about how it behaves on this check.
This audit takes no position on how Clearcote scores on Environment & locale checks, on this one, or anywhere else. It has no baseline corpus of other people's fingerprints to rank you against and no vendor scoreboard — nearly every check is self-referential, asking one browser the same question through two independent APIs and reporting whether both answers can be true at once. It runs identically on any browser, including ours. Run it on yours and read the result yourself.
See the other checks in Environment & locale — the family speech-voices-vs-platform belongs to.