SubtitleEdit/subtitleedit · error · IOException
Kokoro TTS {label} download failed integrity check (expected
Error message
Kokoro TTS {label} download failed integrity check (expected SHA-256 {expected}, got {actual}). What it means
Thrown by KokoroTtsCppDownloadService's integrity check. Unlike the CrispASR services, the expected hash is fetched dynamically via DownloadHashManager.GetLatestKnownHash(key); if empty, verification is skipped. When non-empty, the SHA-256 of the supplied stream is compared case-insensitively — a mismatch raises IOException. The stream Position is rewound to 0 both before and after hashing so the caller can continue using the stream.
Source
Thrown at src/ui/Logic/Download/KokoroTtsCppDownloadService.cs:69
{
if (string.IsNullOrEmpty(key) || stream.Length == 0)
{
return;
}
var expected = DownloadHashManager.GetLatestKnownHash(key);
if (string.IsNullOrEmpty(expected))
{
return;
}
stream.Position = 0;
var actual = await Sha256Util.ComputeSha256Async(stream, cancellationToken);
stream.Position = 0;
if (!string.Equals(expected, actual, StringComparison.OrdinalIgnoreCase))
{
throw new IOException(
$"Kokoro TTS {label} download failed integrity check (expected SHA-256 {expected}, got {actual}).");
}
}
public async Task DownloadModels(string modelsFolder, IProgress<float>? progress, Action<string>? titleProgress, CancellationToken cancellationToken)
{
var ttsPath = Path.Combine(modelsFolder, TtsModelFileName);
var voicesPath = Path.Combine(modelsFolder, VoicesModelFileName);
var needTts = !File.Exists(ttsPath);
var needVoices = !File.Exists(voicesPath);
var total = (needTts ? 1 : 0) + (needVoices ? 1 : 0);
var step = 0;
if (needTts)
{
step++;
titleProgress?.Invoke($"Downloading Kokoro TTS models ({step}/{total}): {TtsModelFileName}");
await DownloadHelper.DownloadFileAsync(_httpClient, TtsModelUrl, ttsPath, progress, cancellationToken);View on GitHub (pinned to 17a9f07487)
Solutions
- Clear the cached download so the service re-fetches the bytes matching the manager's hash.
- Confirm DownloadHashManager.GetLatestKnownHash(key) points at the hash of the artifact currently served upstream; refresh/correct the manager if a rolling update mis-fired.
- Re-download over a clean network path to rule out proxy/AV corruption.
- If rolling hashes are intentional and your local copy is valid-but-old, force a full re-download rather than skipping verification.
Example fix
// before
await kokoroService.DownloadModels(folder, progress, titleProgress, ct);
// after: purge cached tts+voices so hashes match the manager
foreach (var f in new[]{ KokoroTtsCppDownloadService.TtsModelFileName, KokoroTtsCppDownloadService.VoicesModelFileName })
{
var p = Path.Combine(folder, f);
if (File.Exists(p)) File.Delete(p);
}
await kokoroService.DownloadModels(folder, progress, titleProgress, ct); Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-check: skip if no known hash; otherwise the verify IS the check
var expected = DownloadHashManager.GetLatestKnownHash(key);
if (string.IsNullOrEmpty(expected)) { /* verification skipped */ } Try / catch
try { await kokoroService.VerifyAsync(stream, label, key, ct); }
catch (IOException ex) when (ex.Message.Contains("failed integrity check"))
{ stream.SetLength(0); await kokoroService.DownloadModels(folder, progress, titleProgress, ct); } Prevention
- When DownloadHashManager rolls a hash forward, also purge the local cache.
- Log the manager's current hash alongside the actual on mismatch.
- Treat rolling-hash mismatches as 're-download required', not 'corruption'.
When it happens
Trigger: A non-empty latest-known hash exists for the Kokoro TTS download key, and the stream's actual SHA-256 differs: corrupt/truncated download, mirror returned wrong bytes, MITM, or DownloadHashManager's hash advanced to a new build while the local cache holds the old one.
Common situations: DownloadHashManager was updated (rolling hash) to track a newer Kokoro build, but the old bytes are still cached; partial download reported success; AV stripped bytes; proxy rewriting the body. Because hashes come from a manager, a stale manager cache pointing at the wrong build also trips this.
Related errors
- llama.cpp {label} download failed integrity check (expected
- CosyVoice3 (CrispASR) model {fileName} failed integrity chec
- F5-TTS (CrispASR) model {fileName} failed integrity check (e
- IndexTTS (CrispASR) model {fileName} failed integrity check
- Zonos TTS (CrispASR) model {fileName} failed integrity check
AI-assisted analysis of SubtitleEdit/subtitleedit@17a9f07487 (2026-08-13).
Data as JSON: /api/errors/71541a531f7c82ff.
Report an issue: GitHub.