github/copilot-sdk · error

%sRuntimeNodeHash must be a SHA-256 hash

Error message

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

What it means

validateRuntimePairConfig performs the same 32-byte SHA-256 length check for the runtime node library's hash as it does for the wrapper: when both pair members are set but the node hash has the wrong length, Setup panics naming the platform prefix (e.g. "LinuxMuslRuntimeNodeHash"). This guarantees installRuntimePair can verify runtime.node against its digest after writing it.

Solutions

  1. Convert the hex digest with hex.DecodeString so the field holds exactly 32 bytes.
  2. Recompute: h := sha256.Sum256(nodeBytes); cfg.LinuxMuslRuntimeNodeHash = h[:].
  3. Trim whitespace/newlines from the digest string before decoding; assert len(raw) == sha256.Size in the build step.

Example fix

// before
cfg.LinuxMuslRuntimeNodeHash = []byte(strings.TrimSpace(digestHex)) // 64 bytes

// after
raw, _ := hex.DecodeString(strings.TrimSpace(digestHex))
cfg.LinuxMuslRuntimeNodeHash = raw // 32 bytes
Defensive patterns

Strategy: validation

Validate before calling

func validNodeHash(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, "RuntimeNodeHash must be a SHA-256 hash") {
            log.Fatalf("bad runtime node hash: %s", s)
        }
        panic(r)
    }
}()

Prevention

When it happens

Trigger: Calling Setup with the runtime pair present but cfg.LinuxMuslRuntimeNodeHash (or the default RuntimeNodeHash) empty, hex-encoded, or otherwise not exactly 32 raw bytes.

Common situations: Hash generated by a different tool (base64 or hex text); field copied from the executable hash but truncated/emptied during a refactor; CI step that emits the digest with a newline-included string converted to bytes.

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/1a92b03ae43b7cdc. Report an issue: GitHub.

Appendix: source

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

			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
}

func installVerifiedFile(path string, reader io.Reader, expectedHash []byte, mode os.FileMode, label string) error {
	if _, err := os.Stat(path); err == nil {
		existingHash, err := hashFile(path)

View on GitHub (pinned to cd8cf15dc3)