{"record":{"id":"82240e9f4272fe5f","repo":"jdx/mise","slug":"context-sha256-mismatch-expected-sha256-g","errorCode":null,"errorMessage":"#{context}: sha256 mismatch (expected #{sha256}, got #{actual})","messagePattern":"#(.+?): sha256 mismatch \\(expected #(.+?), got #(.+?)\\)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"src/system/packages/brew/shim.rb","lineNumber":87,"sourceCode":"\n  # download with redirects into the cache, verify, return the path\n  def fetch(url, sha256, context)\n    raise \"#{context}: missing sha256\" if sha256.to_s.strip.empty?\n    sha256 = sha256.to_s.strip.downcase\n    raise \"#{context}: malformed sha256\" unless sha256.match?(/\\A[0-9a-f]{64}\\z/)\n\n    MISE_BREW_CACHE.mkpath\n    dest = MISE_BREW_CACHE + \"#{sha256}--#{File.basename(URI(url).path)}\"\n    unless dest.file? && Digest::SHA256.file(dest).hexdigest == sha256\n      ohai \"Downloading #{url}\"\n      tmp = Pathname.new(\"#{dest}.incomplete\")\n      URI.open(url, \"rb\", redirect: true) do |remote|\n        tmp.open(\"wb\") { |f| IO.copy_stream(remote, f) }\n      end\n      actual = Digest::SHA256.file(tmp).hexdigest\n      if actual != sha256\n        tmp.unlink\n        raise \"#{context}: sha256 mismatch (expected #{sha256}, got #{actual})\"\n      end\n      tmp.rename(dest)\n    end\n    dest\n  end\n\n  # unpack an archive the way brew stages sources: if the archive contains a\n  # single top-level directory, its contents become the stage root\n  def unpack(archive, dest)\n    dest.mkpath\n    case archive.basename.to_s\n    when /\\.(tar\\.(gz|xz|bz2|zst)|tgz|txz|tbz2?|tar|crate)\\z/i\n      system_or_die \"tar\", \"xf\", archive.to_s, \"-C\", dest.to_s\n    when /\\.zip\\z/i\n      system_or_die \"unzip\", \"-qo\", archive.to_s, \"-d\", dest.to_s\n    when /\\.(gz|xz|bz2)\\z/i\n      data = `#{archive.to_s =~ /xz\\z/ ? \"xz -dc\" : archive.to_s =~ /bz2\\z/ ? \"bzip2 -dc\" : \"gzip -dc\"} #{Shellwords.escape(archive.to_s)}`\n      raise \"failed to decompress #{archive}\" unless $?.success?","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/packages/brew/shim.rb#L69-L105","documentation":"After downloading an artifact to a temp file, MiseDownload.fetch hashes it and compares against the expected sha256. On mismatch the temp file is deleted and this error is raised, protecting against corrupted, truncated, or tampered downloads; artifacts are only promoted into the content-addressed cache when the digest matches.","triggerScenarios":"The downloaded file at url does not hash to the declared sha256 — wrong URL for the checksum, upstream re-uploaded/changed the artifact, a truncated or MITM-corrupted download, or a checksum copied for a different version.","commonSituations":"Mirror/proxy serving stale files; release artifacts replaced after publication (moving tags); corporate proxies injecting error pages; typos in the checksum after manual entry; downloading 'latest' while the checksum pins an older build.","solutions":["Verify the url and sha256 belong to the exact same artifact/version; recompute sha256sum on a fresh download.","Retry the download — transient truncation can be resolved by clearing the cache entry and fetching again.","Pin a specific immutable version instead of a moving/moving-tag URL so the checksum stays valid.","Check for proxies/antivirus altering responses (curl the URL and compare hashes); bypass or trust the proxy.","If upstream re-released the artifact, update the recipe's checksum to the new verified digest."],"exampleFix":"// before\nMiseDownload.fetch(\"https://example.com/tool-1.2.tar.gz\", \"aaa...old...\", \"tool\")\n// after (recomputed for the actual artifact)\nMiseDownload.fetch(\"https://example.com/tool-1.2.tar.gz\", \"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08\", \"tool\")","handlingStrategy":"validation","validationCode":"# verify checksum outside the shim first\ncurl -fsSL \"$url\" -o /tmp/artifact && echo \"$sha256  /tmp/artifact\" | sha256sum -c -","typeGuard":null,"tryCatchPattern":"begin\n  path = MiseDownload.fetch(url, sha256, context)\nrescue RuntimeError => e\n  raise unless e.message.include?(\"sha256 mismatch\")\n  # re-download, verify upstream checksums, or update the pinned digest\nend","preventionTips":["Pin immutable versioned URLs, never moving tags, so checksums stay valid.","Take digests from upstream published checksum files, not hand transcription.","Investigate proxies/AV if mismatches recur on the same network.","When upstream re-releases, re-verify and update the checksum deliberately."],"tags":["ruby","download","checksum","integrity"],"backgroundTag":"checksum-mismatch","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}