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

  1. Decode the hex hash to raw bytes before Setup: b, _ := hex.DecodeString(hashHex) and pass the 32-byte slice.
  2. Recompute the digest: h := sha256.Sum256(assetBytes); use h[:].
  3. 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

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


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)