LLM Commit Messages

Worktrunk generates commit messages by building a templated prompt and piping it to an external command. This integrates with wt merge, wt step commit, and wt step squash.

LLM commit message generation demo

Setup

Any command that reads a prompt from stdin and outputs a commit message works. Add to ~/.config/worktrunk/config.toml:

Claude Code

[commit.generation]
command = "MAX_THINKING_TOKENS=0 claude -p --no-session-persistence --model=haiku --tools='' --disable-slash-commands --setting-sources='' --system-prompt=''"

--no-session-persistence prevents the commit conversation from polluting claude --continue. The other flags disable tools, skills, settings, and system prompt for fast text-only output. See Claude Code docs for installation.

Codex

[commit.generation]
command = "codex exec -m gpt-5.4-mini -c model_reasoning_effort='low' -c system_prompt='' --sandbox=read-only --json - | jq -sr '[.[] | select(.item.type? == \"agent_message\")] | last.item.text'"

Uses the fast mini model with low reasoning effort and an empty system prompt for faster output. Requires jq for JSON parsing. See Codex CLI docs.

Other tools

# opencode — use a fast model variant
command = "opencode run -m anthropic/claude-haiku-4.5 --variant fast"

# llm
command = "llm -m claude-haiku-4.5"

# aichat
command = "aichat -m claude:claude-haiku-4.5"

Usage

These examples assume a feature worktree with changes to commit.

wt merge

Squashes all changes (uncommitted + existing commits) into one commit with an LLM-generated message, then merges to the default branch:

wt merge
 Squashing 3 commits into a single commit (5 files, +16)...
 Generating squash commit message...
  feat(auth): Implement JWT authentication system
 
  Add comprehensive JWT token handling including validation, refresh
  logic, and authentication tests.
 Squashed @ a1b2c3d
 Merging 1 commit to main @ a1b2c3d (no rebase needed)
  * a1b2c3d feat(auth): Implement JWT authentication system
   auth.rs             | 2 ++
   auth_test.rs        | 2 ++
   integration_test.rs | 6 ++++++
   jwt.rs              | 3 +++
   jwt_test.rs         | 3 +++
   5 files changed, 16 insertions(+)
 Merged to main (1 commit, 5 files, +16)
 Removing feature worktree & branch in background (same commit as main, _)
 Switched to worktree for main @ ~/repo

wt step commit

Stages and commits with LLM-generated message:

wt step commit
 Generating commit message and committing changes... (2 files, +26)
  feat(validation): add input validation utilities
 Committed changes @ a1b2c3d

wt step squash

Squashes branch commits into one with LLM-generated message:

wt step squash
 Squashing 3 commits into a single commit (5 files, +16)...
 Generating squash commit message...
  feat(auth): Implement JWT authentication system
 
  Add comprehensive JWT token handling including validation, refresh
  logic, and authentication tests.
 Squashed @ a1b2c3d

See wt merge and wt step for full documentation.

Branch summaries

With summary = true and a [commit.generation] command configured, Worktrunk generates LLM branch summaries — one-line descriptions of each branch's changes since the default branch.

Summaries appear in:

Enable in user config:

[list]
summary = true

Summaries are cached and regenerated only when the diff changes.

Prompt templates

Worktrunk uses minijinja templates (Jinja2-like syntax) to build prompts.

Custom templates

Override the defaults with inline templates:

[commit.generation]
command = "llm -m claude-haiku-4.5"

template = """
Write a commit message for this diff. One line, under 50 chars.

Branch: {{ branch }}
Diff:
{{ git_diff }}
"""

squash-template = """
Combine these {{ commits | length }} commits into one message:
{% for c in commits %}
- {{ c }}
{% endfor %}

Diff:
{{ git_diff }}
"""

Template variables

VariableDescription
{{ git_diff }}The diff (staged changes or combined diff for squash)
{{ git_diff_stat }}Diff statistics (files changed, insertions, deletions)
{{ branch }}Current branch name
{{ repo }}Repository name
{{ recent_commits }}Recent commit subjects (for style reference)
{{ commits }}Commits being squashed (squash template only)
{{ target_branch }}Merge target branch (squash template only)
{{ user_guidance }}Rendered user template-append fragment (see below)
{{ project_guidance }}Rendered project template-append fragment (see below)

Template syntax

Templates use minijinja, which supports:

See wt config create --help for the full default templates.

Appending to the prompt

template-append adds to the commit and squash prompts instead of replacing them. It lives in both user config (personal preferences) and project config (.config/wt.toml, shared so every teammate's LLM sees the same style guide). Each fragment is itself a minijinja template — Worktrunk renders it with the same variables as the main template ({{ branch }}, {{ git_diff }}, …), then appends the result after <style>. The user fragment renders into a <user-guidance> block and the project fragment into a <project-guidance> block, so the LLM can tell personal preference from shared convention:

# .config/wt.toml
[commit.generation]
template-append = """
- Use conventional commits (feat:, fix:, docs:, …)
- Reference the related issue ID in the body
"""

When both the user and project set template-append, the <user-guidance> block comes first, then <project-guidance>.

The user fragment needs no approval — it's the developer's own config. For the project fragment, the first time the rendered text is sent to the LLM, Worktrunk shows the raw fragment in an approval prompt — the same one-shot gate as project-defined hooks. Subsequent commits don't re-prompt unless the fragment changes. Declining is non-fatal: the LLM runs with just the user fragment (if any).

Custom user templates that don't reference {{ user_guidance }} / {{ project_guidance }} opt out of the appended blocks — the rendered values are injected only where the template places them.

Fallback behavior

When no LLM is configured, worktrunk generates deterministic messages based on changed filenames (e.g., "Changes to auth.rs & config.rs").