Conventional Commits

If you’re running a DevPod-based development environment and want to enforce Conventional Commits across your team, this post walks through how to wire up Commitizen and pre-commit automatically — so every developer gets the right hooks installed the moment they enter the container, with zero manual setup.


Why Conventional Commits?

Conventional Commits give your Git history a consistent, machine-readable structure:

feat(auth): add OAuth2 login support
fix(api): handle null response from upstream
chore(deps): bump helm to 3.14.0

This unlocks automated changelogs, semantic versioning, and better collaboration signals in pull requests. Commitizen adds a CLI wizard to guide you through writing them; pre-commit enforces the format at commit time so nothing slips through.


The Stack

  • mise — polyglot tool version manager (replaces asdf, nvm, pyenv, etc.)
  • Commitizen — conventional commit tooling and CLI (cz)
  • pre-commit — Git hook manager
  • DevPod — open-source dev containers (sets $DEVPOD=true in the environment)

Project Structure

Here’s the relevant file layout:

.
├── mise.toml
├── .pre-commit-config.yaml
└── .devcontainer/
    └── scripts/
        └── setup_project

Step 1: Configure mise Tools and an Enter Hook

mise.toml manages all tool versions declaratively, and supports lifecycle hooks. The enter hook fires whenever you cd into the project directory inside the container:

[tools]
pre-commit = "latest"
python     = "3.13.9"
# ... other tools

[settings]
experimental = true

[hooks]
enter = "bash ./.devcontainer/scripts/setup_project"

Note: The hooks feature requires experimental = true in mise settings.


Step 2: Write the Setup Script

The setup script runs on every enter event, but is gated by two conditions: cz not already being installed, and $DEVPOD=true. This means it’s idempotent (safe to run repeatedly) and only runs inside the DevPod environment — not on local machines where the developer may have their own setup:

#!/bin/bash
if ! command -v cz >/dev/null && [ "$DEVPOD" = "true" ]; then
  git config --global push.autoSetupRemote true
  git config --global --add safe.directory /workspaces/pi5cluster/

  pip install --user pipx
  pipx install commitizen

  pre-commit install
  pre-commit install --hook-type commit-msg
fi

What each line does:

Command Purpose
push.autoSetupRemote true Auto-tracks remote branch on first push
safe.directory Prevents Git “dubious ownership” errors in containers
pip install --user pipx Installs pipx into the user’s Python environment
pipx install commitizen Installs cz in an isolated virtualenv
pre-commit install Registers the pre-commit hook (runs on git commit)
pre-commit install --hook-type commit-msg Registers the commit-msg hook (validates the message)

Step 3: Configure the pre-commit Hook

.pre-commit-config.yaml pins the Commitizen hook to a specific version and attaches it to the commit-msg stage:

repos:
  - repo: https://github.com/commitizen-tools/commitizen
    rev: v4.13.9
    hooks:
      - id: commitizen
        stages: [commit-msg]

This means when a developer runs git commit, the commit message is validated against Conventional Commits format. If it doesn’t conform, the commit is rejected with a clear error.


How It All Fits Together

Here’s the end-to-end flow when a developer opens the project in DevPod:

DevPod container starts
        │
        ▼
mise activates (reads mise.toml)
        │
        ▼
mise fires `enter` hook
        │
        ▼
setup_project script runs
   ├── installs pipx + commitizen  (if first time)
   └── registers pre-commit hooks  (if first time)
        │
        ▼
Developer runs: git commit -m "bad message"
        │
        ▼
commit-msg hook fires → Commitizen validates
   ├── ✅ "feat: add login" → commit succeeds
   └── ❌ "fixed stuff"    → commit rejected

Using Commitizen’s Interactive CLI

Instead of writing commit messages manually, developers can use the cz wizard:

cz commit

This prompts for commit type, scope, description, breaking changes, and more — and writes a perfectly formatted conventional commit message automatically.

my DevOps Odyssey

“Σα βγεις στον πηγαιμό για την Ιθάκη, να εύχεσαι να ‘ναι μακρύς ο δρόμος, γεμάτος περιπέτειες, γεμάτος γνώσεις.” - Kavafis’ Ithaka.