Commit Messages
Commit messages provide a record of why changes were made to the codebase. Clear, consistent commit messages improve collaboration, simplify debugging, and help maintain a useful project history.
Polk County’s projects use the Conventional Commits standard for commit message formatting.
Using a consistent structure makes commit history easier to read and enables tooling such as automated changelog generation.
Commit Message Format
Section titled “Commit Message Format”Commit messages should follow this format:
<type>: <description>
Example:
feat: add permit search endpoint
The type describes the nature of the change, and the description summarizes what the change does.
Commit messages should:
- Be written in present tense
- Use lowercase
- Remain concise and descriptive
- Focus on what the change does, not how it was implemented
Commit Types
Section titled “Commit Types”The following commit types are commonly used in our repositories.
| Type | Description |
|---|---|
feat | Introduces a new feature |
fix | Fixes a bug |
docs | Documentation changes |
style | Code style or formatting changes that do not affect behavior |
refactor | Code restructuring that does not change behavior |
perf | Improves performance |
test | Adds or updates tests |
build | Changes affecting the build system, tooling, or packaging |
ci | Changes to CI/CD configuration or automation workflows |
chore | Maintenance tasks such as dependency updates or tooling changes |
revert | Reverts a previous commit |
Examples
Section titled “Examples”Examples of well-structured commit messages:
feat: add permit search endpointfix: handle null response from permit apidocs: update branching strategy documentationstyle: format code with prettierrefactor: simplify validation logic for permit requestsperf: optimize permit lookup querytest: add unit tests for permit servicebuild: update docker base imageci: update github actions workflowchore: bump go dependenciesrevert: remove permit search endpoint
Dependency Updates
Section titled “Dependency Updates”Most dependency updates should use the chore type.
Use build only when the dependency change directly affects the build system, such as updates to bundlers, compilers, Docker images, or build tooling.
Writing Good Commit Descriptions
Section titled “Writing Good Commit Descriptions”A good commit description should clearly communicate the purpose of the change.
Good examples:
feat: add search filters to permit lookupfix: prevent duplicate permit submissions
Poor examples:
fix stuffupdate codechanges
Descriptions should provide enough context for someone reviewing the commit history to understand what was changed.
Commit Scope (Optional)
Section titled “Commit Scope (Optional)”In some cases, a commit may include an optional scope to indicate the area of the codebase affected.
Format:
<type>(<optional scope>): <description>
Examples:
feat(api): add permit search endpointfix(auth): correct token validation logic
Scopes are optional but can be helpful in larger repositories.
Commit Size
Section titled “Commit Size”Commits should be small and focused.
Recommended practices include:
- Commit related changes together
- Avoid combining unrelated updates in a single commit
- Prefer multiple small commits over a single large commit
Focused commits make it easier to review history and revert changes if necessary.
Squashing Commits
Section titled “Squashing Commits”When merging pull requests, commits are squashed to keep the main branch history clean.
Squashed commits should follow the same Conventional Commits standard.
This ensures the main branch history remains readable and meaningful.
Example squash commit:
feat: add permit search endpoint
What to Avoid
Section titled “What to Avoid”Avoid the following patterns when writing commit messages:
- Vague descriptions
- Multiple unrelated changes in one commit
- Commit messages that describe implementation details instead of outcomes
- Excessively long or unclear descriptions
Clear commit messages improve the maintainability of the project history and help other developers understand past changes.
Commitlint
Section titled “Commitlint”Repositories may use Commitlint to validate commit messages against the Conventional Commits standard.
Commitlint helps enforce consistent commit history by checking that commit messages use approved types and follow the expected format.
Purpose
Section titled “Purpose”Using Commitlint helps:
- Enforce consistent commit message formatting
- Prevent invalid or unclear commit types
- Provide fast feedback during local development
- Support automated validation in CI workflows
Commitlint should be used to reinforce the standards defined in this document, not replace engineering judgment.
Local Setup
Section titled “Local Setup”Commitlint can be installed as a development dependency along with the conventional configuration package and prompt CLI.
npm i @commitlint/cli @commitlint/config-conventional @commitlint/prompt-cli -Dbun add @commitlint/cli @commitlint/config-conventional @commitlint/prompt-cli -dConfigure Commitlint with a commitlint.config.mjs file:
const config = { extends: ["@commitlint/config-conventional"],};
export default config;Repositories may extend this configuration if additional rules are needed.
Git Hook Integration
Section titled “Git Hook Integration”To provide immediate feedback when writing commits, Commitlint can be run through a commit-msg hook.
For repositories using Lefthook, add the following configuration to lefthook.yml:
commit-msg: jobs: - run: npx --no -- commitlint --edit $1commit-msg: jobs: - run: bunx --no -- commitlint --edit $1This validates commit messages before the commit is finalized.
CI Enforcement
Section titled “CI Enforcement”Local hooks provide fast feedback, but CI should be used to ensure commit message rules are enforced consistently.
A GitHub Actions workflow (.github/workflows/commitlint.yml) can be used to validate commit messages on pushes and pull requests:
name: Commit Linter
on: pull_request: push: branches: - main
jobs: commitlint: runs-on: ubuntu-latest
permissions: contents: read
steps: - name: Checkout code uses: actions/checkout@v6 with: fetch-depth: 0
- name: Setup Node.js uses: actions/setup-node@v6 with: node-version: latest
- name: Install dependencies run: npm ci
- name: Validate current commit (push) if: github.event_name == 'push' run: npx commitlint --last --verbose
- name: Validate PR commits (pull_request) if: github.event_name == 'pull_request' run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbosename: Commit Linter
on: pull_request: push: branches: - main
jobs: commitlint: runs-on: ubuntu-latest
permissions: contents: read
steps: - name: Checkout code uses: actions/checkout@v6 with: fetch-depth: 0
- name: Setup Node.js uses: actions/setup-node@v6 with: node-version-file: "package.json"
- name: Setup Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 #v2.2.0 with: bun-version: latest
- name: Install dependencies with Bun run: bun install --frozen-lockfile
- name: Validate current commit (push) if: github.event_name == 'push' run: bunx commitlint --last --verbose
- name: Validate PR commits (pull_request) if: github.event_name == 'pull_request' run: bunx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verboseInteractive Commit Prompts
Section titled “Interactive Commit Prompts”Repositories may also use @commitlint/prompt-cli to guide developers through creating valid commit messages interactively.
To add a convenient script for interactive commits, include the following in package.json:
{ "scripts": { "commit": "commit" }}After staging changes, developers can run the commit script to launch the interactive prompt:
npm run commitbun run commitThis can improve consistency and reduce commit message errors, especially for contributors unfamiliar with the standard.
Summary
Section titled “Summary”Using consistent commit messages helps maintain a clean and understandable project history.
By following the Conventional Commits standard and keeping commits focused, developers can make collaboration, debugging, and long-term maintenance easier for the entire team.