opencode-gitbutler
v0.1.11
Published
OpenCode plugin for GitButler integration
Downloads
243
Maintainers
Readme
opencode-gitbutler
Stop managing git branches manually and let your AI agents do the heavy lifting with GitButler.
Why This Plugin?
AI agents generate code at a pace that manual version control can't match. Without automation, you end up with massive commits, messy branch organization, and generic messages that make code review a nightmare.
This plugin bridges the gap by bringing GitButler's virtual branch power directly into your OpenCode agent sessions.
What This Plugin Does Differently
- Only tool that combines automatic branch creation, LLM commits, file assignment, and context injection.
- Zero-config setup. Just add it to your plugins and go.
- Works with GitButler virtual branches to avoid worktree overhead.
- Impersonates Cursor for full GitButler CLI compatibility.
- Session-first routing: every edit/write is assigned via
but cursor after-edit. - Unique multi-agent session mapping so subagents stay on the parent branch.
- Hunk-level rub guard in post-stop recovery to avoid unsafe auto-moves.
Installation
1. Add plugin to OpenCode config
Add to your opencode.json (global or project-level):
{
"plugin": [
"opencode-gitbutler@latest"
]
}OpenCode will install the plugin automatically on next launch.
2. Install GitButler CLI
brew install gitbutlerSee GitButler installation docs for other methods.
3. Install the GitButler skill (recommended)
The plugin includes a skill that teaches your agent GitButler commands, safety rules, and workflows. Install it as a project-level skill so the agent can load it on demand:
npx skills add https://github.com/gaboe/opencode-gitbutler --skill but --agent opencode --yesWhy? The plugin handles automation (auto-branch, auto-commit), but the agent also needs to know how to use
butcommands directly. Without the skill, the agent falls back to rawgitcommands which break the GitButler workspace.
4. Restart OpenCode
The plugin automatically:
- Routes every
edit/writethrough GitButler'safter-edit - Creates and renames branches based on your prompts
- Rewords commit messages using Claude Haiku (with deterministic fallback)
- Injects workspace state notifications into agent context
- Checks for updates on session creation
How Branch Assignment Works
This plugin uses a session-first flow. In practice:
- On every
edit/write, the plugin resolves your root session (parent session for subagents). - It derives a deterministic
conversation_idfrom that root session (or frombranch_target, when configured). - It always calls
but cursor after-editwith thatconversation_idand file path. - On idle/stop, it calls
but cursor stopfor that sameconversation_id. - In post-stop processing, it may:
- sweep edited files and
but rubunassigned changes when attribution is safe, - reword commit message,
- rename default
ge-branch-*names, - sync OpenCode session title,
- clean empty default branches.
- sweep edited files and
This avoids cross-session branch pollution and keeps subagent edits attached to the parent session branch.
Configuration
Create .opencode/gitbutler.json in your workspace root to override defaults:
{
// Enable debug logging to .opencode/plugin/debug.log
"log_enabled": true,
// LLM provider and model for commit message generation
"commit_message_provider": "anthropic",
"commit_message_model": "claude-haiku-4-5",
// Timeout for LLM requests (milliseconds)
"llm_timeout_ms": 15000,
// Maximum diff size to send to LLM (characters)
"max_diff_chars": 4000,
// Maximum length of auto-generated branch slugs
"branch_slug_max_length": 50,
// Enable automatic version update checks
"auto_update": true,
// Regex pattern for default branch detection
"default_branch_pattern": "^ge-branch-\\d+$",
// Milliseconds before file lock is considered stale
"stale_lock_ms": 300000,
// Max age of pending notifications before expiry
"notification_max_age_ms": 300000,
// Enable branch inference heuristics in post-stop sweep
"inference_enabled": true,
// Optional: force all sessions onto one branch seed
"branch_target": "",
// Reserved (currently no-op)
"edit_debounce_ms": 200,
"gc_on_session_start": false
}Configuration Reference
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| log_enabled | boolean | true | Write debug logs to .opencode/plugin/debug.log |
| commit_message_provider | string | "anthropic" | LLM provider ID |
| commit_message_model | string | "claude-haiku-4-5" | Model ID for commit generation |
| llm_timeout_ms | number | 15000 | Request timeout in milliseconds |
| max_diff_chars | number | 4000 | Max diff size sent to LLM |
| branch_slug_max_length | number | 50 | Max auto-generated branch name length |
| auto_update | boolean | true | Check npm for newer versions |
| default_branch_pattern | string | "^ge-branch-\\d+$" | Regex for default branch detection |
| stale_lock_ms | number | 300000 | Lock age threshold before stale cleanup |
| notification_max_age_ms | number | 300000 | Expiry window for queued state notifications |
| inference_enabled | boolean | true | Enable branch inference in post-stop sweep |
| branch_target | string | unset | Force all sessions to one branch seed (disables per-session isolation) |
| edit_debounce_ms | number | 200 | Reserved, currently no-op |
| gc_on_session_start | boolean | false | Reserved, currently no-op |
All fields are optional. Missing fields use defaults.
Feature Parity vs Native Integrations
How this plugin compares to GitButler's built-in Cursor and Claude Code integrations:
| Feature | Cursor | Claude Code | This Plugin | Status |
|---------|--------|-------------|-------------|--------|
| Post-edit hook | after-edit | PostToolUse | tool.execute.after | Equal |
| Stop/idle hook | stop | Stop | session.idle | Equal |
| Branch creation | get_or_create_session | get_or_create_session | via conversation_id | Equal |
| Auto-assign to existing branch | Internal | Internal | Session-first after-edit + safe post-stop but rub sweep | Better |
| Branch auto-rename (LLM) | From Cursor DB | From transcript | but reword + user prompt | Equal |
| Auto-commit on stop | handle_changes() | handle_changes() | via but cursor stop | Equal |
| Commit message (LLM) | OpenAI gpt-4-mini | OpenAI gpt-4-mini | Claude Haiku via OpenCode SDK | Equal |
| Multi-agent session mapping | — | — | resolveSessionRoot() | Unique |
| File locking (concurrent) | — | 60s wait + retry | 60s poll + stale cleanup | Equal |
| Agent state notifications | — | — | chat.messages.transform | Unique |
| Hunk-level rub guard | — | — | Skip multi-stack files | Better |
Score: 7 Equal, 4 Better/Unique
For the full architecture breakdown, gap analysis, and known issues, see docs/gitbutler-integration.md.
Known Operational Limits
- The plugin only performs GitButler actions in workspace mode (
gitbutler/workspacebranch). - If
branch_targetis set, all sessions intentionally share one branch seed. edit_debounce_msandgc_on_session_startare reserved config fields and are currently no-op.- GitButler CLI still has upstream edge cases around unapply/pull after squash-merge with deleted remote branches (see linked issues in
docs/gitbutler-integration.md).
Troubleshooting
GitButler CLI not found
Error: ⚠ GitButler CLI not found. Install with: brew install gitbutler
Solution: Install GitButler via Homebrew:
brew install gitbutlerThe plugin will work without it, but workspace commands will fail at runtime.
Config file not found
If .opencode/gitbutler.json is missing, the plugin uses all defaults. No error is raised.
Debug logging
Enable log_enabled: true in config to write detailed logs to .opencode/plugin/debug.log. Useful for diagnosing branch creation, commit message generation, and state injection issues.
Wrong branch assignment
If changes still appear on an unexpected branch:
- Ensure
branch_targetis not set in.opencode/gitbutler.json. - Check
.opencode/plugin/session-map.jsonand confirm subagent sessions resolve to the expected parent. - Inspect
.opencode/plugin/debug.logforafter-edit,session-stop, andbranch-collisionevents. - Run
but status --json -fand verify where the file is currently assigned.
LLM timeout
If commit message generation times out, increase llm_timeout_ms in config:
{
"llm_timeout_ms": 30000
}Large diffs
If diffs are truncated, increase max_diff_chars:
{
"max_diff_chars": 8000
}Workspace Guide
See SKILL.md bundled with this package for detailed GitButler workspace commands, multi-agent safety rules, and known issues.
License
MIT
