Skip to content

Git – Commit Messages

A commit message is a permanent record in the project's history. It will be read during code review, during debugging, in release notes, and by engineers who haven't yet joined the team. A great commit message explains what changed and — more importantly — why. A poor one says update or fix stuff.

All Cygnus Dynamics repositories follow the Conventional Commits specification. This format is machine-readable (enabling automated changelogs and release versioning) and human-readable at a glance.

Format

<type>(<scope>): <short description>

[optional body — explain WHY, not what, wrap at 100 chars]

[optional footer — issue references, breaking change notes]

The Three Parts

Part Required Purpose
type(scope): description Yes One-line summary — appears in changelogs and PR lists
Body No Explains the reasoning, context, and trade-offs
Footer No Links to tickets, flags breaking changes

Commit Types

Type When to Use Example
feat A new feature visible to users or API consumers feat(auth): add OAuth2 login with Google
fix A bug fix fix(payments): handle declined card on timeout
refactor Code restructure — no behaviour change, no bug fix refactor(order): extract validation into OrderValidator
perf Performance improvement perf(query): add index on orders.user_id
test Adding or updating tests test(user): add coverage for inactive account login
docs Documentation only docs(api): add pagination examples to orders endpoint
chore Build process, dependency updates, tooling chore(deps): upgrade Spring Boot to 3.2.1
style Formatting, whitespace — no logic change style(user): apply spotless formatting
ci CI/CD pipeline changes ci(pipeline): add sonar quality gate to PR checks
revert Reverts a previous commit revert: feat(auth): add OAuth2 login with Google

Scope

The scope is optional but strongly encouraged. It names the part of the codebase affected — a feature, module, layer, or service:

feat(auth): ...
fix(payments): ...
refactor(order-service): ...
chore(deps): ...
ci(azure-pipeline): ...

Keep scopes consistent across the project. If your team uses user-service in one commit and users in another, changelogs become fragmented. Agree on scope names per repo and stick to them.

Writing the Short Description

  • Imperative mood — "add login" not "added login" or "adds login"
  • 72 characters or fewer — it must fit in git log --oneline, GitHub PR lists, and Azure DevOps displays
  • No capital first letter after the colon — feat(auth): add OAuth2 not feat(auth): Add OAuth2
  • No period at the end
  • Be specific — what exactly changed?
# ✅ Good — specific, imperative, clear
feat(auth): add JWT refresh token rotation on expiry
fix(orders): prevent duplicate charge when payment gateway times out
refactor(user): extract email validation into shared EmailValidator utility
perf(dashboard): replace N+1 query with single JOIN on order summary

# ❌ Bad — vague, past tense, too generic
fix: fixed bug
update: updated stuff
WIP
feat: changes
feature work
asdfgh

Writing the Body

The body explains why the change was made — the reasoning, the problem being solved, and any trade-offs. It is not a line-by-line description of what the diff contains. The diff shows what changed; the body explains why.

Wrap lines at 100 characters. Separate the body from the subject with a blank line.

fix(payments): prevent double-charge on gateway timeout

Stripe's payment gateway occasionally returns a 504 timeout after the
charge has actually been processed. Previously, our retry logic would
attempt the charge again on any non-2xx response, causing duplicate
charges on affected customer accounts.

This fix adds idempotency key generation scoped to the order ID and
timestamp. Duplicate requests with the same key are safely rejected by
Stripe without processing a second charge.

The 504 path is now retried once with the same idempotency key,
which Stripe handles correctly by returning the original charge result.
feat(orders): add cursor-based pagination to order list endpoint

Offset pagination on the orders table was causing full-table scans at
high page numbers. For accounts with 100k+ orders, pages beyond 500
were timing out.

Replaced with keyset pagination using the created_at + id composite
cursor. This makes all pages equally fast regardless of depth and
eliminates the need for a COUNT(*) query on every request.

The existing ?page=&limit= parameters are preserved for backward
compatibility and continue to work for the first 50 pages. Beyond
that, the cursor parameter is required.

The footer references issue tracking tickets and flags breaking changes:

# ✅ Issue reference — links the commit to its ticket
Closes: PROJ-1234
Refs: PROJ-5678

# ✅ Breaking change — must be flagged explicitly
BREAKING CHANGE: The /api/v1/orders endpoint now requires the
Authorization header. Requests without a valid JWT will receive
a 401 response. Clients must be updated before deploying.

Real-World Examples

feat(auth): add rate limiting to login endpoint

Brute-force attempts on the login endpoint were detected in the
March 2026 security review. This adds a 10-attempt-per-hour limit
per IP address using Redis-backed sliding window rate limiting.

Accounts locked by this limit return a 429 with a Retry-After header
indicating when the window resets. The limit resets automatically —
no manual unlock required.

Closes: PROJ-2847
chore(deps): upgrade axios from 1.4.0 to 1.7.9

Addresses CVE-2024-39338 (SSRF vulnerability in redirect handling).
No API changes — the upgrade is backward compatible.

Refs: PROJ-3102
fix(user-profile): trim whitespace from email on save

Users pasting email addresses from clipboard sometimes include
trailing spaces, causing login failures because the stored value
doesn't match the typed value at login time.

The fix trims the email string before validation and before persistence.
Existing affected rows are cleaned up by migration V042.

Closes: PROJ-1991

Rules Summary

Rule ✅ Correct ❌ Incorrect
Format type(scope): description update user page
Mood add login page added login page
Length ≤ 72 characters 90+ character subject
Capital after colon lowercase Uppercase
Period No period fix: handle null.
Body vs diff Explains why Restates what changed
Ticket reference Closes: PROJ-1234 Ticket number buried in body prose

Pre-commit enforcement

Install commitlint with the @commitlint/config-conventional ruleset to automatically reject non-conforming commit messages before they are pushed. This eliminates the most common review comment across the entire team:

npm install --save-dev @commitlint/config-conventional @commitlint/cli husky