github/copilot-sdk · error

%sRuntimeExecutableHash must be a SHA-256 hash

Error message

%sRuntimeExecutableHash must be a SHA-256 hash (%d bytes), got %d bytes

What it means

After confirming both members of a runtime pair are present, validateRuntimePairConfig checks that the wrapper executable's hash is exactly 32 bytes (SHA-256 size). A hash of any other length cannot be used by the verify-on-install pipeline, so Setup panics with the platform prefix (e.g. "LinuxMuslRuntimeExecutableHash") embedded in the message. This fail-fast check precedes any file writes.

Solutions

  1. Decode the hex digest to 32 raw bytes with hex.DecodeString before assigning it to the hash field.
  2. Recompute it directly: h := sha256.Sum256(wrapperBytes); cfg.LinuxMuslRuntimeExecutableHash = h[:].
  3. Verify the digest source produces raw SHA-256 (32 bytes), not base64/hex text or a different algorithm's output.

Example fix

// before
cfg.LinuxMuslRuntimeExecutableHash = []byte(hashHex) // 64 bytes

// after
raw, err := hex.DecodeString(hashHex)
if err != nil || len(raw) != sha256.Size { /* fix pipeline */ }
cfg.LinuxMuslRuntimeExecutableHash = raw
Defensive patterns

Strategy: validation

Validate before calling

func validWrapperHash(h []byte) bool { return len(h) == 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, "RuntimeExecutableHash must be a SHA-256 hash") {
            log.Fatalf("bad runtime executable hash: %s", s)
        }
        panic(r)
    }
}()

Prevention

When it happens

Trigger: Calling Setup where cfg.LinuxMuslRuntimeExecutableHash (or the default RuntimeExecutableHash) is a hex string's bytes, an empty slice, or a non-SHA-256 digest while the corresponding executable and node are both set.

Common situations: Storing hashes as hex strings and forgetting hex.DecodeString; using a SHA-1 or truncated digest from an older pipeline; copy-pasting a hash across platforms with mismatched 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/59696deb731f0046. Report an issue: GitHub.

Appendix: source

Thrown at go/internal/embeddedcli/embeddedcli.go:425

		hash := sha256.Sum256(content)
		mode := os.FileMode(header.Mode & 0777)
		if err := installVerifiedFile(path, bytes.NewReader(content), hash[:], mode, "runtime asset"); err != nil {
			return err
		}
	}
	runtimeAssetsInstalled = true
	return nil
}

func validateRuntimePairConfig(wrapper io.Reader, wrapperHash []byte, node io.Reader, nodeHash []byte, prefix string) {
	if (wrapper == nil) != (node == nil) {
		panic(prefix + "RuntimeExecutable and " + prefix + "RuntimeNode must be provided together")
	}
	if wrapper == nil {
		return
	}
	if len(wrapperHash) != sha256.Size {
		panic(fmt.Sprintf("%sRuntimeExecutableHash must be a SHA-256 hash (%d bytes), got %d bytes", prefix, sha256.Size, len(wrapperHash)))
	}
	if len(nodeHash) != sha256.Size {
		panic(fmt.Sprintf("%sRuntimeNodeHash must be a SHA-256 hash (%d bytes), got %d bytes", prefix, sha256.Size, len(nodeHash)))
	}
}

func installRuntimePair(installDir string) (string, error) {
	nodePath := filepath.Join(installDir, "runtime.node")
	if err := installVerifiedFile(nodePath, config.RuntimeNode, config.RuntimeNodeHash, 0644, "runtime.node"); err != nil {
		return "", err
	}
	wrapperPath := filepath.Join(installDir, runtimeExecutableName())
	if err := installVerifiedFile(wrapperPath, config.RuntimeExecutable, config.RuntimeExecutableHash, 0755, "runtime wrapper"); err != nil {
		return "", err
	}
	return wrapperPath, nil
}

View on GitHub (pinned to cd8cf15dc3)