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.
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:
wt switchinteractive picker — preview tab 5wt list --full— the Summary column (seewt list)
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
| Variable | Description |
|---|---|
{{ 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:
- Variables:
{{ branch }},{{ repo | upper }} - Filters:
{{ commits | length }},{{ repo | upper }} - Conditionals:
{% if recent_commits %}...{% endif %} - Loops:
{% for c in commits %}{{ c }}{% endfor %} - Loop variables:
{{ loop.index }},{{ loop.length }} - Whitespace control:
{%- ... -%}strips surrounding whitespace
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").