SubtitleEdit/subtitleedit · error · IOException

Qwen3 TTS {label} download failed integrity check (expected

Error message

Qwen3 TTS {label} download failed integrity check (expected SHA-256 {expected}, got {actual}).

What it means

Integrity check for the Qwen3 TTS (cpp) engine download. Expected hash comes from DownloadHashManager.GetLatestKnownHash(key); the freshly downloaded stream is hashed in place (rewound to 0 before and after) and compared case-insensitively. Mismatch throws IOException.

Source

Thrown at src/ui/Logic/Download/Qwen3TtsCppDownloadService.cs:96

    {
        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(
                $"Qwen3 TTS {label} download failed integrity check (expected SHA-256 {expected}, got {actual}).");
        }
    }

    public async Task DownloadModels(string modelsFolder, string ttsModelFileName, IProgress<float>? progress, Action<string>? titleProgress, CancellationToken cancellationToken)
    {
        if (!TtsModelUrls.TryGetValue(ttsModelFileName, out var ttsModelUrl))
        {
            throw new ArgumentException($"Unknown Qwen3 TTS model: {ttsModelFileName}", nameof(ttsModelFileName));
        }

        var ttsModelPath = Path.Combine(modelsFolder, ttsModelFileName);
        var tokenizerPath = Path.Combine(modelsFolder, TokenizerModelFileName);
        var needTts = !File.Exists(ttsModelPath);
        var needTokenizer = !File.Exists(tokenizerPath);
        var total = (needTts ? 1 : 0) + (needTokenizer ? 1 : 0);
        var step = 0;

View on GitHub (pinned to 17a9f07487)

Solutions

  1. Refresh DownloadHashManager so the latest known hash matches the current artifact.
  2. Delete and re-download the affected file.
  3. Verify the engine artifact version the binary expects vs. the one the CDN serves.
  4. Check for truncation from network/disk.
Defensive patterns

Strategy: validation

Validate before calling

var expected = DownloadHashManager.GetLatestKnownHash(key);
if (!string.IsNullOrEmpty(expected))
{
    stream.Position = 0;
    var actual = await Sha256Util.ComputeSha256Async(stream, ct);
    stream.Position = 0;
    if (!string.Equals(expected, actual, StringComparison.OrdinalIgnoreCase))
    {
        await DownloadHashManager.RefreshAsync(ct);
    }
}

Try / catch

try { await service.DownloadAsync(...); }
catch (IOException ex) when (ex.Message.Contains("Qwen3 TTS") && ex.Message.Contains("integrity check"))
{
    await DownloadHashManager.RefreshAsync(ct);
    if (File.Exists(dest)) File.Delete(dest);
    await service.DownloadAsync(...);
}

Prevention

When it happens

Trigger: A non-empty expected hash from the manager that differs from the SHA-256 of the downloaded stream. Empty expected skips the check.

Common situations: Stream not actually rewound before hashing elsewhere (here it is), a partial download, an updated origin artifact that the hash manager hasn't tracked yet, or a corrupt cache.

Related errors


AI-assisted analysis of SubtitleEdit/subtitleedit@17a9f07487 (2026-08-13). Data as JSON: /api/errors/782881c9f34d2b9d. Report an issue: GitHub.