Commit Granularity with Agent Code
The atomic commit principle
An atomic commit is the smallest meaningful change that cannot be broken down further while still serving a single purpose. The commit should be self-contained, reversible, and describable in a single sentence.
Traditional advice on atomic commits isn't new. What's new is that agentic development makes atomicity practical.
Before agents, writing atomic commits meant discipline. Each small change required the full overhead: switch context, verify tests, stage files, craft message, commit. The friction encouraged batching. Developers accumulated changes into larger commits to reduce the overhead-to-content ratio.
Agents flip this. Generation is fast; verification is the bottleneck. Each verification naturally creates a commit boundary. The friction now favors smaller commits because each validation becomes a checkpoint.
Why granularity increases with agents
Several factors push commits smaller in agentic workflows.
Verification cadence. The previous page established that each commit represents validated agent output rather than accumulated typing. When you verify every few minutes, you commit every few minutes. The natural unit of work shrinks from "logical feature" to "verified step."
Rollback needs. Agent-generated code has 41% higher churn than human-written code. More changes get revised within two weeks. Smaller commits mean reverting one specific change rather than untangling a mixed bag. A focused revert removes only what it describes. A mixed revert causes collateral damage.
Review pressure. CodeRabbit found 1.7x more issues in agent-assisted PRs. Reviewers examining 500 changed lines across 50 files struggle to spot problems. The same 500 lines split into 20 commits of 25 lines each become tractable. Each commit tells a single story. Reviewers can evaluate that story in isolation.
Defining granularity in practice
"Atomic" can mean different things to different teams. These guidelines provide practical boundaries.
One logical change per commit. If the commit message requires "and" or a comma-separated list, the commit is too large. "Add authentication middleware" works. "Add authentication middleware and update user schema and fix session timeout" does not.
Compilable state. Each commit should leave the codebase buildable.
Tests don't all need to pass, but the build shouldn't break.
This keeps git bisect functional. Binary search for bugs requires each step to be runnable.
Reviewable in minutes, not hours. If a single commit takes more than a few minutes to understand, it's too large. The 200-400 lines of code guideline from research applies here. Smaller is better when the changes are coherent.
Not artificially fragmented. Atomic doesn't mean microscopic. A button component needs its HTML, CSS, and JavaScript together. Splitting them into three commits fragments context that belongs together. The atomic unit is "add the button," not "add button HTML" + "add button styles" + "add button behavior."
Agent granularity patterns
Different agents produce different commit patterns. Understanding these helps calibrate expectations.
Aider takes the most granular approach: one commit per edit. Whenever Aider edits a file, it commits those changes with a descriptive message. This creates a fine-grained history where each agent action is a separate commit. The history shows exactly what the agent changed and when. Rollback is surgical.
Claude Code operates on user direction. It doesn't auto-commit after every edit. The developer decides when to checkpoint. This allows grouping related agent actions into logical commits. The tradeoff: you must remember to commit frequently. The advantage: commits reflect human judgment about logical groupings.
Codex works in sandboxed isolation, generating code across a task, then presenting a pull request. Commits within that PR may vary. Some implementations produce granular commits, others batch the entire task. The emphasis is on PR-level review rather than commit-level inspection.
GitKraken's Commit Composer addresses a different problem. It takes unstaged changes or existing commits and suggests restructuring into cleaner logical chunks. The AI analyzes changes and proposes a reorganized commit structure. Developers can work messily, then organize before sharing.
The readability tradeoff
More granular commits produce more history. More history creates navigation overhead.
A repository with 50,000 commits remains tractable. Major open-source projects routinely contain hundreds of thousands of commits. The Linux kernel has over one million. Raw commit count isn't the problem.
History noise is the problem. When every agent invocation creates a commit, the log fills with:
e8f9d2a Add user service method
b3c4e5f Fix typo in user service
a1b2c3d Update user service signature
9f8e7d6 Add missing importThese four commits might represent a single logical change with three corrections. Reading history, the story becomes obscure. What was the actual intent? Which corrections matter for understanding the design decision?
The solution isn't to batch during development. It's to organize before sharing.
When to squash
Squashing combines multiple commits into one. The technique has specific applications in agentic workflows.
Before merging to main. Feature branches often contain iteration history that doesn't aid understanding. Squashing the branch into a single commit (or a small set of logical commits) cleans the main branch history. The detailed history remains accessible through the merge commit or archived branch.
For correction chains. When multiple commits represent iterations on the same logical change (add, fix, fix, fix), squashing combines them into the final correct version. The corrections were important during development. They're noise in the permanent record.
For work-in-progress markers. Commits with messages like "WIP" or "checkpoint" served their purpose during development. Before sharing, these should merge into meaningful commits with proper messages.
Not for distinct changes. Squashing unrelated changes together recreates the large mixed commit problem. If changes were distinct enough to warrant separate commits, they're distinct enough to remain separate.
When not to squash
Preservation matters in certain contexts.
When debugging history. git bisect requires each commit to be buildable and testable.
Squashed history provides fewer points for binary search.
If a bug exists in a 500-line squashed commit, you know the bug is somewhere in those 500 lines.
If the same changes were 20 commits, you can narrow to 25 lines.
When attribution matters. Compliance requirements may need audit trails showing what was generated when. Squashing obscures this timeline. If the question is "when did this code enter the repository and in what form," granular history answers precisely.
When sharing active branches. Rebasing or squashing commits on branches that others have pulled causes synchronization problems. Other developers' history no longer matches the rewritten version. On shared branches, history is effectively immutable.
The squash-before-PR pattern
A practical pattern that captures the benefits of both approaches:
-
During development: Commit frequently. Every verification creates a checkpoint. Message quality matters less. The priority is rollback capability.
-
Before PR submission: Review the commit history. Identify logical groupings. Squash correction chains. Rewrite vague messages. The result should tell a clear story.
-
During review: The PR presents a clean logical history. Reviewers evaluate distinct, well-explained changes. Comments can reference specific commits.
-
On merge: Depending on repository policy, squash-merge to main or preserve the curated commit history.
This pattern follows the principle: "commit often, perfect later, publish once." Development history is private working state. Published history is communication.
Maintaining coherent history
Long-term repository health depends on history that tells a comprehensible story.
Use git log --first-parent for main branch navigation. This shows only merge commits, providing a high-level view without every commit from every branch.
The detailed history exists but doesn't clutter the primary view.
Enforce commit message standards. Consistent format, whether Conventional Commits, imperative mood, or team convention, makes history scannable. Readers can filter by prefix, search for types of changes, and skim efficiently.
Document large changes externally. Major refactors, architecture changes, and significant features deserve documentation beyond commit messages. The commit links to the discussion. The discussion explains the context.
Archive rather than delete. When cleaning up experimental branches, consider archiving rather than deleting.
git tag archive/feature-experiment branch-name preserves access.
git branch -d branch-name removes the branch pointer.
The history remains accessible through the tag.
Practical configuration
Claude Code can follow commit conventions when directed.
Creating a .claude/commands/commit.md file defines commit guidelines:
Generate commit messages following these rules:
- Use imperative mood ("Add feature" not "Added feature")
- First line under 72 characters
- One logical change per commit
- Reference issue numbers when applicableGitKraken and similar tools offer AI commit message generation that analyzes staged changes. Review these suggestions before accepting. AI-generated commit messages, like AI-generated code, need human verification.
The goal is consistent, meaningful history. Whether achieved through convention, automation, or curation, the result should be a repository where future readers can understand what happened and why.