Skip to main content
Technology & EngineeringGit Workflow123 lines

Conventional Commits

Conventional Commits specification for structured, machine-readable commit messages

Quick Summary36 lines
You are an expert in the Conventional Commits specification for structured, machine-readable commit messages.

## Key Points

- `feat` — a new feature (correlates with MINOR in semver)
- `fix` — a bug fix (correlates with PATCH in semver)
- `docs` — documentation-only changes
- `style` — formatting, white-space, semicolons (no logic change)
- `refactor` — code restructuring without fixing a bug or adding a feature
- `perf` — performance improvement
- `test` — adding or updating tests
- `build` — changes to the build system or external dependencies
- `ci` — changes to CI configuration
- `chore` — maintenance tasks that don't modify src or test files
- Keep the description line under 72 characters; use the body for details and motivation
- Use scopes consistently within a project by defining an allowed list in commitlint config

## Quick Example

```
<type>[optional scope]: <description>

[optional body]

[optional footer(s)]
```

```bash
git commit -m "feat(cart): add quantity selector to cart items"
git commit -m "fix(api): handle null response from payments service"
git commit -m "docs: update API authentication guide"
git commit -m "refactor(db): extract connection pooling into shared module"
git commit -m "ci: add Node 22 to test matrix"
```
skilldb get git-workflow-skills/Conventional CommitsFull skill: 123 lines
Paste into your CLAUDE.md or agent config

Conventional Commits — Git Workflows

You are an expert in the Conventional Commits specification for structured, machine-readable commit messages.

Overview

Conventional Commits is a lightweight convention on top of commit messages that provides a consistent format. It enables automated tooling for changelogs, semantic versioning, and release management. The format is: <type>[optional scope]: <description>.

Core Concepts

Commit message structure:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

Standard types:

  • feat — a new feature (correlates with MINOR in semver)
  • fix — a bug fix (correlates with PATCH in semver)
  • docs — documentation-only changes
  • style — formatting, white-space, semicolons (no logic change)
  • refactor — code restructuring without fixing a bug or adding a feature
  • perf — performance improvement
  • test — adding or updating tests
  • build — changes to the build system or external dependencies
  • ci — changes to CI configuration
  • chore — maintenance tasks that don't modify src or test files

Breaking changes:

# Using footer
git commit -m "feat(auth): switch to OAuth 2.1

BREAKING CHANGE: removed password-based login endpoint"

# Using ! shorthand
git commit -m "feat(auth)!: switch to OAuth 2.1"

Implementation Patterns

Commitlint setup (Node.js projects):

npm install --save-dev @commitlint/{cli,config-conventional}

# commitlint.config.js
echo "module.exports = { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js

# Enforce via Husky hook
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'

Automated releases with semantic-release:

{
  "release": {
    "branches": ["main"],
    "plugins": [
      "@semantic-release/commit-analyzer",
      "@semantic-release/release-notes-generator",
      "@semantic-release/changelog",
      "@semantic-release/npm",
      "@semantic-release/git",
      "@semantic-release/github"
    ]
  }
}

Example commits:

git commit -m "feat(cart): add quantity selector to cart items"
git commit -m "fix(api): handle null response from payments service"
git commit -m "docs: update API authentication guide"
git commit -m "refactor(db): extract connection pooling into shared module"
git commit -m "ci: add Node 22 to test matrix"

Core Philosophy

Conventional Commits exists because commit messages are an API for tooling and a historical record for humans, and most commit messages fail at both. A message like "fix stuff" tells neither a human reviewer nor an automated release tool what changed or why. By imposing a lightweight structure — type(scope): description — the convention makes every commit message machine-parseable (enabling automated changelogs and semantic versioning) and human-scannable (the type immediately communicates the nature of the change). The constraint is minimal but the downstream benefits compound across the entire release pipeline.

The type system is not arbitrary — it maps directly to semantic versioning decisions. A feat commit triggers a minor version bump. A fix commit triggers a patch version bump. A BREAKING CHANGE footer triggers a major version bump. This mapping eliminates the subjective, error-prone process of deciding version numbers manually. When the version is derived deterministically from the commit history, the release process becomes fully automatable: merge to main, let CI analyze the commits since the last release, compute the next version, generate the changelog, publish the package. No human judgment required, no version number debates.

Enforcing the convention at the commit hook level (with commitlint and Husky) is essential because a convention that is not enforced is a convention that erodes. One non-conforming commit message breaks automated changelog generation for that release. Over time, unenforced conventions decay until they provide none of the intended benefits while still being cited as a team "standard." The enforcement should be instant and local — the developer sees the rejection immediately when they commit, not ten minutes later when CI fails.

Anti-Patterns

  • Wrong type classification. Using fix for a new feature or refactor for a bug fix corrupts automated versioning. A feature labeled as a fix ships as a patch release, which violates semver and confuses consumers who expect backward compatibility between patches. Choose the type that accurately describes the change's impact, not its implementation effort.

  • Descriptions that state the "what" instead of the "why". Writing fix(api): update handler function tells the reader nothing about the bug that was fixed. Writing fix(api): return 404 instead of 500 for missing resources communicates the actual change in behavior. The description should explain the impact, not narrate the diff.

  • Skipping enforcement. Adopting Conventional Commits as a team convention without installing commitlint and a commit-msg hook means non-conforming messages slip in regularly, breaking automated changelog generation and semantic versioning. Enforce the convention at the git hook level so violations are caught before push.

  • Scope inconsistency. Using scopes haphazardly — auth in one commit, authentication in another, login in a third — fragments the changelog and makes it impossible to filter commits by component. Define an allowed scope list in the commitlint configuration and enforce it.

  • Overusing chore as a catch-all. Labeling everything that is not clearly a feat or fix as chore hides meaningful distinctions between documentation updates (docs), CI changes (ci), build system changes (build), and test improvements (test). Each type exists for a reason; use the one that most accurately describes the change.

Best Practices

  • Keep the description line under 72 characters; use the body for details and motivation
  • Use scopes consistently within a project by defining an allowed list in commitlint config
  • Automate enforcement with commitlint and a commit-msg hook so non-conforming messages are rejected before push

Common Pitfalls

  • Choosing the wrong type (e.g., using fix for a feature or refactor for a bug fix), which corrupts automated versioning
  • Writing descriptions that state what changed instead of why, reducing the value of the commit log as a historical record

Install this skill directly: skilldb add git-workflow-skills

Get CLI access →