multica-ai/multica · error

%s must be valid UTF-8

Error message

%s must be valid UTF-8

What it means

Content validation from skillContentBytesToString: the resolved bytes are not valid UTF-8. Skill content is transmitted as a JSON string to the API, so non-UTF-8 bytes (binary files, wrong-encoding text) cannot be carried and are rejected client-side before any network call. The label names the offending source.

Source

Thrown at server/cmd/multica/cmd_skill.go:226

	if filePath != "" {
		data, err := os.ReadFile(filePath)
		if err != nil {
			return "", false, fmt.Errorf("read file for --content-file: %w", err)
		}
		return skillContentBytesToString(data, "file content for --content-file")
	}
	if inlineSet {
		return inline, true, nil
	}
	return "", false, nil
}

func skillContentBytesToString(data []byte, label string) (string, bool, error) {
	if len(data) == 0 {
		return "", false, fmt.Errorf("%s is empty", label)
	}
	if !utf8.Valid(data) {
		return "", false, fmt.Errorf("%s must be valid UTF-8", label)
	}
	return string(data), true, nil
}

func runSkillList(cmd *cobra.Command, _ []string) error {
	client, err := newAPIClient(cmd)
	if err != nil {
		return err
	}

	ctx, cancel := cli.APIContext(context.Background())
	defer cancel()

	var skills []map[string]any
	if err := client.GetJSON(ctx, "/api/skills", &skills); err != nil {
		return fmt.Errorf("list skills: %w", err)
	}

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Check the encoding: `file -i <path>`; convert with `iconv -f <src-encoding> -t UTF-8`.
  2. If the payload is genuinely binary, it does not belong in skill content — store it elsewhere and reference it by path/URL.
  3. Re-save the source file as UTF-8 in your editor before re-running the command.

Example fix

# before
multica skill create --name s --content-file legacy-latin1.md
# after
iconv -f ISO-8859-1 -t UTF-8 legacy-latin1.md > skill.utf8.md
multica skill create --name s --content-file skill.utf8.md
Defensive patterns

Strategy: validation

Validate before calling

# Reject non-UTF-8 or binary input before the CLI
case "$(file -b --mime-encoding "$CONTENT_FILE")" in utf-8|us-ascii) ;; *) echo "not UTF-8"; exit 2;; esac
# or in Go: if !utf8.Valid(data) { return errors.New("content must be UTF-8") }

Type guard

func isValidSkillContent(data []byte) bool {
    return len(data) > 0 && utf8.Valid(data)
}

Try / catch

Match 'must be valid UTF-8'; convert encoding with iconv or exclude the binary payload, then retry — the raw bytes cannot be made valid by escaping.

Prevention

When it happens

Trigger: Passing a binary or Latin-1/CP1252-encoded file via --content-file, or piping compiled/binary output through --content-stdin.

Common situations: Assuming skill content can be any file type; text files saved in legacy encodings from Windows tools; accidentally pointing --content-file at an archive or image.

Related errors


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/535d91c22c4485e5. Report an issue: GitHub.