kubernetes/kubernetes · error
unexpected merge commit format: %q
Error message
unexpected merge commit format: %q
What it means
Validation error from getMergeCommitInfo when the first line of the commit body does not match the expected GitHub merge-commit format 'Merge pull request #<n> from <branch>' (main.go:440-442). After trimming, lines[0] is split into fields; fewer than 4 fields triggers this. The %q is the offending first line.
Source
Thrown at hack/apidiff-changelog/main.go:442
func outputName(path string) string {
re := regexp.MustCompile(`[^a-zA-Z0-9_-]`)
return re.ReplaceAllString(path, "_") + ".out"
}
// getMergeCommitInfo extracts the PR title and description from a GitHub merge commit.
// The merge commit body is expected in the format produced by GitHub:
// first line "Merge pull request #<number> from <branch>", last line is the PR title.
func getMergeCommitInfo(commit string) (title, description string, err error) {
cmd := exec.Command("git", "show", "--no-patch", "--format=%B", commit)
out, err := cmd.Output()
if err != nil {
return "", "", fmt.Errorf("git show %s: %w", commit, err)
}
lines := trimTrailingEmpty(strings.Split(strings.TrimRight(string(out), "\n"), "\n"))
// First line: "Merge pull request #<number> from <branch>".
words := strings.Fields(lines[0])
if len(words) < 4 {
return "", "", fmt.Errorf("unexpected merge commit format: %q", lines[0])
}
prNum := strings.TrimPrefix(words[3], "#")
title = lines[len(lines)-1]
description = fmt.Sprintf("See [PR #%s](https://github.com/kubernetes/kubernetes/pull/%s).", prNum, prNum)
return title, description, nil
}
// trimTrailingEmpty removes trailing empty strings from a slice.
func trimTrailingEmpty(lines []string) []string {
for len(lines) > 0 && lines[len(lines)-1] == "" {
lines = lines[:len(lines)-1]
}
return lines
}
// compareApidiff runs apidiff on two module state files, prints the formatted
// comparison report to stdout, and returns the incompatible (non-tolerated)
// changes for changelog verification. Returns an empty string if there are none.View on GitHub (pinned to b882c60b40)
Solutions
- Pass the actual merge commit SHA (the one whose message starts with 'Merge pull request #...').
- If the repo only squash-merges, do not use -merge-commit — populate the changelog title/description manually via -update-changelog then edit.
- Inspect the commit: `git show --no-patch --format=%B <commit>` and confirm the first line shape.
Example fix
# before (squash-merge commit) $ git show --no-patch --format=%B abc123 Add DeviceTaintRules API # -> first run: unexpected merge commit format: "Add DeviceTaintRules API" # after: use -update-changelog and edit the placeholder instead $ ./apidiff-changelog -base main -update-changelog pkg/client
Defensive patterns
Strategy: validation
Validate before calling
// Confirm the commit body matches GitHub's merge format before passing it in.
func looksLikeGHMerge(body string) bool {
words := strings.Fields(strings.TrimSpace(body))
return len(words) >= 4 && words[0] == "Merge" && words[1] == "pull" && words[2] == "request"
} Type guard
null
Try / catch
words := strings.Fields(lines[0])
if len(words) < 4 {
return "","", fmt.Errorf("unexpected merge commit format: %q", lines[0])
} Prevention
- Only pass -merge-commit for real GitHub merge commits.
- For squash-merges, use -update-changelog and edit the placeholder.
- Inspect the commit body first: `git show --no-patch --format=%B <sha>`.
When it happens
Trigger: -merge-commit points at a commit whose body's first line is not a GitHub-style merge line. Examples: a squash-merge commit (first line is the PR title), a direct (non-PR) commit, a commit created with a custom merge message, or an empty body.
Common situations: Repo uses squash-merges instead of merge-commits (very common in many Kubernetes sub-projects); passing the PR head commit instead of the merge commit; passing a regular commit SHA; GitHub 'Merge branch' style merges.
Related errors
- git show %s: %w
- DRA driver name is empty
- static pods may not use ClusterTrustBundle projected volume
- static pods may not use ResourceClaims
- concurrent-ephemeralvolume-syncs must be greater than 0, but
AI-assisted analysis of kubernetes/kubernetes@b882c60b40 (2026-08-07).
Data as JSON: /api/errors/3d856f254d862bcc.
Report an issue: GitHub.