In over 30 years of using source control, I've never once worked on something where it's useful to include the component (article calls it scope) in the description in a standardised way. It's obvious what components are affected based on where in the source tree the affected files are. Similarly "bug", "fix" or "feature" adds no useful value. It's important or it wouldn't be checked in.
The only thing I've found useful, and which the article doesn't even consider, is a link / id for the relevant change request. The commit already contains all the information about what was done in the change, what's missing is the context about why.
Even on my solo projects I include a JIRA reference in square brackets before the description. If it's just something I randomly decided to fix during the course of development, I'll create a short 1 line JIRA to get an id and explain the why there.
If I only worked with seasoned devs, I wouldn’t use it, but that’s just the reality of my work. It also has a bonus of forcing AI agents to write in the same form as well instead of their random personal flavor. Precommit hooks stop everything before it gets in front of my eyes for review.
I can not say anything nice about conventional commits. The format takes up space in the most-read part of the message. The categories or types have little information. They can be replaced with an honest English verb embedded in the subject like a sentence. It also reads way better with just a sentence instead of three kinds of punctuation (:, (), !). Okay, I can tolerate an "area" in the subject. And that predates this conventio.
At my dayjob we make a webapp for non technical people. I can write a changelog for that just fine (in norwegian). The commit messages are irrelevant to the users. And demanding that all commits should be good enough for an end-user changelog? That's not happening for us anytime soon.
Use footers/trailers instead.
However, actually writing a good commit message is an art form few have mastered.
I wrote a small natural language linter to teach my teams meaningful technical writing: https://github.com/codingjoe/word-weasel
If one needs to put metadata in commits, usually better to just put it in a Git trailer.
https://git-scm.com/docs/git-interpret-trailers
`Co-authored-by: Alice` is a common one, but you can have anything in there.
And it’s ugly.
(But I suppose I am talking primarily about the first line part. The “BREAKING CHANGE” bit is potentially actually useful, though being incompatible with git-interpret-trailers despite leaning on git-interpret-trailers for other footers seems a bit crazy.)
This makes neovim plugin manager highlight the change differently which brings attention to it when you update stuff.
So please do use it instead of complaining!
I do like the suggestion of
scope!: ...
if it will be treated the same way with breaking changes reactions.
To me this is almost the most important information in a commit message. I don't know how often in the last 15 years I was cross checking the issue description referenced by some old commit to get the full context of a change. I also felt that this habit is kind of standard - until i had to learn about "conventional commits".
I never got the hype.
[0] https://www.kernel.org/doc/html/v7.0/process/submitting-patc...
A changelog is user-facing”
I'd say that ship has probably sailed. Most companies are happy with “Bug Fixes & Performance Improvements”. At least if they're not going to put the effort in, then a generated changelog is better than nothing.
EDIT: I didn't see this covered in the article on my first pass. It is covered though. My apologies.
The type of the commit informs the automated workflows how to handle the commit. This is why it comes first.
For example, if you're performing CD, if you only commit a bunch of `fix: ` then only your semantic versioning patch version number is incremented. If you commit a `feat: ` then it's a minor version is bump. `feat! ` is a major version bump.
Even if you're not using CD for releases, semantic commit messages are sometimes used to automate change log generation. Granted, your change logs should not typically include the Git commit messages themselves — those are developer facing, not user facing.
I fully recognise that it doesn't make sense for huge projects like the Linux kernel to do this. But for 99% of projects conventional commits combined with semver vastly improves the release process status quo and makes it easy to automate.
> fix: prevent foo from bar'ing
The whole idea of conventional commit is:
> fix: [problem]
so the correct conventional commit would be:
> fix: foo bar'ing
which is succinct and perfectly fine.
Conventional commits are most valuable to me as historical context rather than as a release-management tool.
The larger the project becomes, the more useful that context gets.
Keep a change log.
> something like fix, feat, chore, docs, or refactor
'Docs' are also part of the program, they need fixes too, and features need docs. If the docs don't match the features because they're not being updated when the code is, the docs are a lie and waste other developers time.
Also if you were writing a standard: why would you randomly abbreviate 'feature' but not 'refactor'? That sounds like a nitpick but standards require great thought, this is a bit of a smell that there hasn't been much thought into designing 'conventional commits'.
Finally: the name 'Conventional commits' is a land grab (reminds me of when someone made a JS Standard and called it 'StandardJS', ignoring every existing popular standard). From the article, the *actual* convention is 'scope: work"
- Linux
subsystem: description
- FreeBSD prefix: description
- Git area: description
- Go package: description
- nixpkgs pkg-name: description fix thing in foo
Issue: ABC-123
Git has plenty of builtins for parsing and formatting these trailers, so you can easily create custom git log aliases that let you see them inline and parse them for use with CI.Personally (without conventional commits) I tend to put them at the end in parentheses if the commit has something to do with that issue. But if there is a stronger relationship like that it fixes the issue, I put a `Fixes` trailer in the message as well.
conventional commits are pleasing, but questionable actual utility. the code speaks for itself. the actually useful information is a well chosen title and the context for the change.
> fix: prevent racing of requests
Though the example in the actual specification, “fix: array parsing issue when multiple spaces were contained in string”, is more inconclusive (and frankly doesn’t really make sense as a description).
And the issue isn't whether you can remember what you changed yesterday; this is largely about making sure other developers can quickly identify relevant commits. If you're a solo non-OSS developer, this is entirely relevant to you.
Could/should the changelog be considered a first-class deliverable with care and attention provided? I think so, but I'm not in a position to exert direct control over that across dozens of repos and team members.
It's fine for it to be in the description.
Same idea without the pejorative aspect.
I don't like conventional commits much neither, but let the people use whatever they want!
There is a meme that inspires some of this genre of title, fwiw
So now I associate it an automated pr vs authored
Funny to ask to stop doing something I don't do or never even heard of. I typically only mark database schema migrations or other major things with special prefixes.
# Bug Fixes
- foo bar'ing
In my experience, LLMs are great at reviewing changelogs for potential gaps from a user POV (and even creating draft changelogs wholesale, if you're backfilling) based on git history.
Ill add: I am personally put off in the same way your parent comment is, because hard stances are usually wrong, and I like a bit of nuance in my life.
Why on Earth are people not writing commit messages for their reverts? They should have semantic commit messages just the same as any other commit.
Unless the point is that they're not following per-commit CD, and if you commit then revert that commit before a release was made. That sounds like a process failure. Which of course, process isn't infallible, and neither is the automated version management. If you screw up, use an escape hatch — just like reverting a commit that had previously gone through code review and been merged.
Re: change log generation. The article says change logs shouldn't have commit messages. I agree. Many tools (e.g. Changesets https://github.com/changesets/changesets) use the semantic commit type to sort change log entries, but require you to write those user facing change log entries separately.
You’ve almost certainly encountered Conventional Commits before. It may have reared its ugly head in the changelog of an open source project you’ve used. It may have been the enforced commit format for an open source project you contributed to. A lot of people swear by it. I swear at it.
Even though it is used by a large number of popular open source projects, Conventional Commits is an actively bad standard which encourages focus on the wrong things and fails to deliver on its promises.
Conventional Commits promises to add semantic meaning to commit messages to aid developers and end-users in understanding the changes made in a commit. However, Conventional Commits fails to do this in spectacular fashion. To demonstrate this, let’s look at the anatomy of a conventional commit. According to the Conventional Commit website commit messages should be formatted as follows:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
The commit’s subject line has a <type> (something like fix, feat, chore, docs, or refactor1) describing the type of change. Following that, there is an optional scope, and then a description.
This format has a major failing: type is prioritised over scope. This is exactly backwards.
The scope of a change (the subject of the change) is the most important part of a commit. To demonstrate this, let’s consider why each one of the following stakeholders care about the scope of the change more than the type of the change:
Contributors: when you are a contributor to a project, you often need to read the commit log to identify changes in the codebase relevant to a certain area of the code. There are many reasons for this including:
As you read the commit log, you’re looking at what areas were touched. You really do not care about the type of change happening, you care about the scope of the change.
Debuggers: when investigating a bug, you often want to look through the commit log to see what changes might have touched areas related to the component where the bug manifested. Once again, the scope is the most important piece of information. The type of change is entirely useless because bugs can be introduced in any change regardless of type. (I’m sure we’ve all experienced writing a bugfix that caused another bug.)
Incident responders: when production is down, scanning the commit log for changes that were made around the time of the outage is an effective way to identify what areas may be causing the problem. Scope is once again the most important piece of information you can have at this point. For example, if you see a commit related to the auth scope at the tip of the spike of inbound API errors, it’s a likely culprit for the problem. And once again, type is irrelevant because bugs could have been added by any change.
So what does Conventional Commits do? It deprioritises scope so much that it’s optional! Why the hell is scope optional? Having a commit without a scope is like having a sentence without a subject! Then, to add insult to injury, Conventional Commits elevates type to the front of the commit message. Conventional Commits gets the priority of scope and type entirely wrong.
You might be thinking “so it may be backwards, but commit type is at least still important, right?” and to that I say “no”. A commit’s description should almost always tell you the type of the change! Consider this commit message as an example:
fix(compiler): prevent namespaced SVG <style> elements from being stripped
Even if you only had the description, it’s obvious that it was a bugfix! Space on the subject line of a commit is already at a premium, wasting characters on the type is not helpful! But it’s often even worse than useless; it’s often restrictive. Take this commit message as an example:
refactor(core): Update webmcp support to use document.modelContext
This commit updated the webmcp functionality in the core component to support both document.modelContext and navigator.modelContext, so was that a bugfix, refactor, or new feature? I would argue it’s all of them! But again, the only thing that really matters is that it was a change to the core/webmcp component.
Conventional Commits fundamentally focuses on the wrong thing (the commit type) and devalues the scope (which is what people actually care about).
So we have determined that the format of Conventional Commits sucks, but it must provide some benefit. Let’s read the Why Use Conventional Commits section to see if any of the reasons make any sense.
Automatically generating CHANGELOGs.
This is the biggest promise of Conventional Commits: you can run a tool like git-cliff or conventional-changelog to generate a changelog from the commits since your last release. Is this even a good idea? No! The audience of a changelog is entirely different than the audience for a commit log!
A changelog is user-facing, and the user cares about understanding the functional differences between versions. They care about what changed from a business/functional perspective.
A commit log is developer-facing, and the developers care about reading a story of how the codebase has changed over time. They care about what changed from a scope perspective.
As you can see, these are two entirely different grains, and any efforts to combine them result in subpar results. The reasons for this are multiple:
Automatically determining a semantic version bump (based on the types of commits landed).
This sounds nice, but the realities of software engineering often interfere significantly with the viability of accurately accomplishing this task. Consider the following situations:
In such situations, you could rewrite history with a rebase, but that often breaks or is prevented by workflows. It also presents a revisionist history to the contributors trying to contribute to the project, reducing the reliability of the story the commit log is telling.
Communicating the nature of changes to teammates, the public, and other stakeholders.
As we have established up to this point, teammates and the public have very different needs from a changelog and commit log. Conventional Commits manages to solve neither.
Triggering build and publish processes.
This is just a bad idea. Say you only run automated security checks on commits that touch code and then someone creates a Trojan-horse commit titled docs: fix typos which actually introduces vulnerabilities into the authentication subsystem? Obviously, that sort of malicious activity would hopefully be caught in code review, but the automated tooling is bypassed, putting the onus on a human to identify the problem.
Compute is cheap, just use git diff to identify changed files (scope, once again) and run build/publish processes based on that.
Making it easier for people to contribute to your projects, by allowing them to explore a more structured commit history.
More structured, sure. Making it easier to contribute? Not at all (as we have already demonstrated at length).
Not a single one of the “selling points” for Conventional Commits actually holds water.
Conventional Commits is also extremely difficult to apply to a project. You are supposed to define your own set of “types”, but pretty much everyone just takes the defaults from commitlint which often don’t fit well with the particulars of individual projects. This problem is especially acute in corporate environments where change management and audit requirements often mandate a ticket number in every commit message. The <scope> field is the obvious place to put it, but this ends up replacing the only useful metadata in a Conventional Commit with a completely useless ticket number.
So what should you do instead? Follow the lead of truly successful software projects like Linux, FreeBSD, Git, Go, and NixOS! What do these projects have in common? They all use scope-prefixed commit messages (where “scope” is defined to be relevant to the actual project). Usually, the scope to use on a given project is self-evident. For the Linux kernel, the subsystem is the natural scope. For Go projects, the package path is the natural scope. For a project using a microservice architecture, the microservice name is the natural scope.
Here are some examples of projects and their commit format guidelines.
| Project | Format | Example |
|---|---|---|
| Linux | subsystem: description |
i2c: virtio: mark device ready before registering the adapter |
| FreeBSD | prefix: Description |
linuxulator: Return EINVAL for invalid inotify flags |
| Git | area: description |
gitlab-ci: update macOS image |
| Go | package: description |
net/http/cookiejar: add godoc links |
| nixpkgs | pkg-name: description |
xwayland: 24.1.11 -> 24.1.12 |
Unfortunately, despite being used by some of the most successful open source projects ever created, this commit style seems to have lost the branding war. I intend to change that. Introducing scopedcommits.com. The website is dedicated to advocating for a return to commit message sanity, and separating the concern of changelog generation from commit log management.
Conventional Commits’ purported advantages are actually illusory and the industry has seen no tangible benefit from using it as a standard. However, Conventional Commits unfortunately seems to have become fairly popular in open source projects, and due to this it seems like AIs have a habit of defaulting to using it for commit messages. This has caused propagation of anti-pattern-ridden commit messages across projects.
My goal in this article is to fight against Conventional Commits’ dominance, and demonstrate that there better ways to structure commit messages. But if this article has not convinced you to stop using Conventional Commits, I look forward to the flame war in the comment section.
fix and feat and leaves additional types up to individual projects to specify, however most projects just end up using the types defined by commitlint, so I have included some of them in this list. ↩︎If you are releasing upon every push to main/master (following what semantic release and conventional commits provides you in terms of automation), then it makes sense to perform major version bumps for the reverts.
If you have a manual release strategy, then it might not make sense to use these tools in the way they have been designed.
And if you don’t have these kinds of dependents, then the versioning scheme isn’t important anyway.