Git Integration for Agent Workflows
Git as Agent Infrastructure
Git isn't just for version control - it's critical infrastructure for agent harnesses:
Anthropic emphasizes:
> "Agents commit changes with descriptive messages and leverage git to revert problematic changes."
Git Patterns for Agents
Pattern 1: Atomic Commits
Each logical change gets its own commit:
class AgentGitManager {
// Commit after each discrete change
async commitChange(
description: string,
files?: string[]
): Promise<string> {
// Stage specific files or all changes
if (files) {
for (const file of files) {
await bash(`git add "${file}"`);
}
} else {
await bash('git add .');
}
// Check if there's anything to commit
const status = await bash('git status --porcelain');
if (!status.trim()) {
console.log('Nothing to commit');
return '';
}
// Create descriptive commit message
const message = this.formatCommitMessage(description);
await bash(`git commit -m "${message}"`);
const hash = await bash('git rev-parse --short HEAD');
console.log(`đ Committed: ${hash} - ${description}`);
return hash.trim();
}
private formatCommitMessage(description: string): string {
// Follow conventional commits
const type = this.inferCommitType(description);
return `${type}: ${description}`;
}
private inferCommitType(description: string): string {
const lower = description.toLowerCase();
if (lower.includes('add') || lower.includes('create')) return 'feat';
if (lower.includes('fix') || lower.includes('bug')) return 'fix';
if (lower.includes('update') || lower.includes('change')) return 'chore';
if (lower.includes('test')) return 'test';
if (lower.includes('doc')) return 'docs';
return 'feat';
}
}
Pattern 2: Checkpoint Commits
Create checkpoints at key moments:
async function implementFeatureWithCheckpoints(feature: Feature) {
const git = new AgentGitManager();
// Checkpoint: Starting feature
await git.commitChange(`Start ${feature.name}`);
// Implement steps
for (let i = 0; i < feature.steps.length; i++) {
const step = feature.steps[i];
// Do the work
await implementStep(step);
// Checkpoint after each step
await git.commitChange(`${feature.name}: ${step.description}`);
// Run tests
const testResult = await runTests();
if (!testResult.passed) {
// Rollback this step
await git.rollbackLastCommit();
console.log(`â ī¸ Step ${i + 1} failed tests, rolled back`);
// Try again with different approach
await implementStepAlternative(step);
await git.commitChange(`${feature.name}: ${step.description} (alt approach)`);
}
}
// Final checkpoint
await git.commitChange(`Complete ${feature.name}`);
}
Pattern 3: Feature Branches
Isolate work on feature branches:
class AgentBranchManager {
async startFeature(featureId: string): Promise<void> {
const branchName = `feature/${featureId}`;
// Ensure we're on main and up to date
await bash('git checkout main');
await bash('git pull origin main');
// Create and switch to feature branch
await bash(`git checkout -b ${branchName}`);
console.log(`đŋ Created branch: ${branchName}`);
}
async completeFeature(featureId: string): Promise<void> {
const branchName = `feature/${featureId}`;
// Ensure all changes committed
const status = await bash('git status --porcelain');
if (status.trim()) {
throw new Error('Uncommitted changes exist');
}
// Merge to main
await bash('git checkout main');
await bash(`git merge ${branchName} --no-ff -m "Merge ${featureId}"`);
// Delete feature branch
await bash(`git branch -d ${branchName}`);
console.log(`â
Merged and cleaned up: ${branchName}`);
}
async abandonFeature(featureId: string): Promise<void> {
const branchName = `feature/${featureId}`;
// Switch to main
await bash('git checkout main');
// Force delete the branch
await bash(`git branch -D ${branchName}`);
console.log(`đī¸ Abandoned branch: ${branchName}`);
}
}
Rollback Strategies
Strategy 1: Soft Rollback (Last Commit)
async rollbackLastCommit(): Promise<void> {
// Keep changes in working directory
await bash('git reset --soft HEAD~1');
console.log('âŠī¸ Rolled back last commit (changes preserved)');
}
Strategy 2: Hard Rollback (Last Commit)
async hardRollbackLastCommit(): Promise<void> {
// Discard all changes
await bash('git reset --hard HEAD~1');
console.log('âŠī¸ Rolled back last commit (changes discarded)');
}
Strategy 3: Rollback to Specific Checkpoint
async rollbackToCheckpoint(commitHash: string): Promise<void> {
// First, check how many commits we're reverting
const log = await bash(`git log --oneline ${commitHash}..HEAD`);
const commitsToRevert = log.trim().split('\n').length;
console.log(`âŠī¸ Rolling back ${commitsToRevert} commits to ${commitHash}`);
// Hard reset to checkpoint
await bash(`git reset --hard ${commitHash}`);
}
Strategy 4: Safe Rollback with Backup
async safeRollback(targetHash: string): Promise<string> {
// Create backup branch first
const backupBranch = `backup/${Date.now()}`;
await bash(`git branch ${backupBranch}`);
console.log(`đĻ Created backup: ${backupBranch}`);
// Now safe to rollback
await bash(`git reset --hard ${targetHash}`);
return backupBranch; // Return backup branch name for potential recovery
}
Using Git History for Context
Agents can read git history to understand project state:
class AgentGitContextReader {
// Get recent work summary
async getRecentWorkSummary(count: number = 10): Promise<string> {
const log = await bash(
`git log --oneline --no-decorate -${count}`
);
return log;
}
// Find when a file was last modified
async getFileLastModified(filePath: string): Promise<GitCommit> {
const log = await bash(
`git log -1 --format="%H|%s|%ai" -- "${filePath}"`
);
const [hash, message, date] = log.split('|');
return { hash, message, date };
}
// See what changed in last commit
async getLastCommitChanges(): Promise<string[]> {
const diff = await bash('git diff --name-only HEAD~1 HEAD');
return diff.trim().split('\n').filter(Boolean);
}
// Find commits related to a feature
async findFeatureCommits(featureName: string): Promise<string> {
const log = await bash(
`git log --oneline --grep="${featureName}"`
);
return log;
}
// Get diff for code review
async getCommitDiff(commitHash: string): Promise<string> {
return await bash(`git show ${commitHash} --stat`);
}
}
E-Commerce Agent Git Workflow
async function ecommerceAgentWorkflow(feature: Feature) {
const git = new AgentGitManager();
const branch = new AgentBranchManager();
const context = new AgentGitContextReader();
// 1. Read current state from git
const recentWork = await context.getRecentWorkSummary();
console.log('đ Recent commits:', recentWork);
// 2. Start feature branch
await branch.startFeature(feature.id);
// 3. Implement with checkpoints
try {
for (const step of feature.steps) {
// Checkpoint before risky operation
const checkpoint = await git.commitChange(`checkpoint: before ${step.name}`);
try {
await implementStep(step);
await git.commitChange(`${feature.name}: ${step.name}`);
// Verify with tests
const tests = await runTests();
if (!tests.passed) {
throw new Error(`Tests failed: ${tests.error}`);
}
} catch (error) {
console.log(`â Step failed: ${error.message}`);
// Rollback to checkpoint
await git.rollbackToCheckpoint(checkpoint);
// Try alternative approach
console.log('đ Trying alternative approach...');
await implementStepAlternative(step);
await git.commitChange(`${feature.name}: ${step.name} (alternative)`);
}
}
// 4. Complete feature
await branch.completeFeature(feature.id);
} catch (error) {
console.log(`đĢ Feature failed: ${error.message}`);
// Abandon the feature branch
await branch.abandonFeature(feature.id);
throw error;
}
}
Git Hooks for Agents
Use git hooks to enforce quality:
# .git/hooks/pre-commit
#!/bin/bash
# Run type checking
npm run typecheck
if [ $? -ne 0 ]; then
echo "â Type check failed"
exit 1
fi
# Run linting
npm run lint
if [ $? -ne 0 ]; then
echo "â Lint failed"
exit 1
fi
echo "â
Pre-commit checks passed"
// Agent respects hooks
async commitWithHooks(message: string): Promise<boolean> {
try {
await bash('git add .');
await bash(`git commit -m "${message}"`);
return true;
} catch (error) {
if (error.message.includes('pre-commit')) {
console.log('â ī¸ Pre-commit hook failed, fixing issues...');
// Auto-fix what we can
await bash('npm run lint:fix');
// Try again
await bash('git add .');
await bash(`git commit -m "${message}"`);
return true;
}
throw error;
}
}
Commit Message Convention
Agents should follow consistent commit conventions:
interface CommitMessage {
type: 'feat' | 'fix' | 'chore' | 'test' | 'docs' | 'refactor';
scope?: string;
description: string;
body?: string;
footer?: string;
}
function formatCommitMessage(msg: CommitMessage): string {
let result = `${msg.type}`;
if (msg.scope) {
result += `(${msg.scope})`;
}
result += `: ${msg.description}`;
if (msg.body) {
result += `\n\n${msg.body}`;
}
if (msg.footer) {
result += `\n\n${msg.footer}`;
}
// Agent attribution
result += '\n\nGenerated by AI Agent';
return result;
}
// Example usage
const commit = formatCommitMessage({
type: 'feat',
scope: 'cart',
description: 'add quantity update functionality',
body: 'Users can now update item quantities directly in the cart.\nIncludes validation for stock limits.',
footer: 'Closes #42'
});
Key Takeaways
- Git is infrastructure - Not just version control for agents
- Atomic commits enable rollbacks - One change per commit
- Feature branches isolate experiments - Safe to fail
- Checkpoints are safety nets - Commit before risky operations
- Git history provides context - Agents can read their own work
- Hooks enforce quality - Catch issues before commit
Practice Exercise
Design a git workflow for an agent building a REST API:
- Define the branching strategy
- Create checkpoint rules (when to commit)
- Implement rollback decision logic
- Write commit message templates
- Add a pre-commit hook for API validation
Next Lesson
Now let's explore Building with Claude Agent SDK - Anthropic's official library for creating production agent harnesses.