github/copilot-sdk · error
must be a SHA-256 hash ( bytes), got bytes
Error message
%s must be a SHA-256 hash (%d bytes), got %d bytes
What it means
validateOptionalHash enforces that when an optional embedded asset reader (e.g. RuntimeAssets, LinuxMuslRuntimeAssets) is provided, its companion hash must be exactly a SHA-256 digest (32 bytes). If a non-nil reader is paired with a hash of any other length, Setup panics so misconfigured integrity data is caught at startup rather than at install time. This protects the verify-on-write pipeline that compares written bytes against the digest.
Solutions
- Decode the hex hash to raw bytes before Setup: b, _ := hex.DecodeString(hashHex) and pass the 32-byte slice.
- Recompute the digest: h := sha256.Sum256(assetBytes); use h[:].
- If no hash is available, pass nil for the reader rather than a reader with a bogus hash.
Example fix
// before
cfg.RuntimeAssetsHash = []byte("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855") // 64 bytes
// after
raw, _ := hex.DecodeString("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
cfg.RuntimeAssetsHash = raw // 32 bytes Defensive patterns
Strategy: validation
Validate before calling
func validOptionalHash(reader io.Reader, hash []byte) bool {
return reader == nil || len(hash) == sha256.Size
} Type guard
func isSHA256(b []byte) bool { return len(b) == sha256.Size } Try / catch
defer func() {
if r := recover(); r != nil {
if s, ok := r.(string); ok && strings.Contains(s, "must be a SHA-256 hash") {
log.Fatalf("embedded asset hash misconfigured: %s", s)
}
panic(r)
}
}() Prevention
- Store hashes as [32]byte or []byte of raw digest, never hex/base64 strings.
- Add a build-time/unit test asserting len(hash) == sha256.Size for every embedded asset.
- hex-decode at the point the digest is produced by CI, not at Setup time.
When it happens
Trigger: Calling Setup with cfg.RuntimeAssets non-nil but cfg.RuntimeAssetsHash set to a hex string's bytes, a truncated/short digest, an empty slice, or a SHA-1/MD5 digest instead of the raw 32-byte SHA-256.
Common situations: Passing a hex-encoded hash string (64 chars → 64 bytes) instead of the decoded 32 bytes; copying a hash field from a different asset; build tooling emitting the wrong digest length.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- %sRuntimeExecutableHash must be a SHA-256 hash
- %sRuntimeNodeHash must be a SHA-256 hash
- checksum mismatch for
- CliHash must be a SHA-256 hash
- %sRuntimeExecutable and %sRuntimeNode must be provided…
AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09).
Data as JSON: /api/errors/11afe769c2dbb0c2.
Report an issue: GitHub.
Appendix: source
Thrown at go/internal/embeddedcli/embeddedcli.go:359
return "", fmt.Errorf("creating install directory: %w", err)
}
if release, _ := flock.Acquire(filepath.Join(installDir, ".copilot-cli.lock")); release != nil {
defer release()
}
path, err := installRuntimePair(installDir)
if err != nil {
return "", err
}
if err := installRuntimeAssets(installDir); err != nil {
return "", err
}
return path, nil
}
func validateOptionalHash(reader io.Reader, hash []byte, name string) {
if reader != nil && len(hash) != sha256.Size {
panic(fmt.Sprintf("%s must be a SHA-256 hash (%d bytes), got %d bytes", name, sha256.Size, len(hash)))
}
}
func installRuntimeAssets(installDir string) error {
if config.RuntimeAssets == nil || runtimeAssetsInstalled {
return nil
}
archiveBytes, err := io.ReadAll(config.RuntimeAssets)
if closer, ok := config.RuntimeAssets.(io.Closer); ok {
closer.Close()
}
if err != nil {
return fmt.Errorf("reading runtime assets: %w", err)
}
actual := sha256.Sum256(archiveBytes)
if !bytes.Equal(actual[:], config.RuntimeAssetsHash) {
return fmt.Errorf("runtime assets hash mismatch")
}View on GitHub (pinned to cd8cf15dc3)