Skip to content

Conversation

ahdzib-maya
Copy link
Contributor

@ahdzib-maya ahdzib-maya commented May 20, 2025

Pull Request: Add Multi-Instance Support for Claude Code

Description

This PR adds multi-instance support to the Claude Code Neovim plugin, allowing users to run multiple Claude Code instances simultaneously - one per git repository root. This feature improves workflow when working across multiple
projects in different tabs or windows.

Type of Change

Please check the options that are relevant:

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring
  • Performance improvement
  • Other (please describe):

Checklist

Please check all that apply:

  • I have read the CONTRIBUTING document
  • My code follows the style guidelines of this project
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have tested with the actual Claude Code CLI tool
  • I have tested in different environments (if applicable)

Additional Notes

Key changes include:

  • Refactored terminal buffer management to support multiple instances
  • Changed from a single buffer reference to a table mapping git roots to buffer numbers
  • Added configuration option git.multi_instance (defaults to true)
  • Updated CLAUDE.md documentation with details and examples
  • Modified buffer naming to include git root path for easier identification
  • Added support for working across multiple Neovim tabs with different projects

Summary by CodeRabbit

  • New Features

    • Added support for running multiple Claude Code instances concurrently, each tied to a separate git repository, enabling parallel work across different Neovim tabs.
    • Buffer names now include the git root path for easier identification in multi-instance mode.
    • Introduced a configurable option to enable or disable multi-instance support (enabled by default).
  • Documentation

    • Updated documentation with a new section on multi-instance support, including configuration examples.
  • Refactor

    • Centralized buffer number retrieval and improved terminal management to support multiple independent instances.

Copy link

coderabbitai bot commented May 20, 2025

Walkthrough

The changes introduce multi-instance support to the Claude Code plugin, allowing multiple independent terminal instances keyed by git root or working directory. Documentation is updated to describe this feature and its configuration. The codebase is refactored to manage multiple buffers, update buffer naming, and centralize buffer retrieval logic, while ensuring configuration validation for the new option.

Changes

File(s) Change Summary
CLAUDE.md Added documentation for "Multi-Instance Support", describing configuration, usage, and buffer naming.
lua/claude-code/config.lua Added multi_instance boolean config option (default: true), updated validation, documented new field.
lua/claude-code/init.lua Refactored buffer retrieval logic using a new local helper for current buffer number; updated toggle logic.
lua/claude-code/terminal.lua Overhauled terminal management for multi-instance support: instance mapping, buffer naming, and toggling.
lua/claude-code/file_refresh.lua Updated file refresh timer callback to use current instance buffer number instead of single buffer number.
lua/claude-code/keymaps.lua Modified terminal navigation setup to retrieve buffer from current instance instead of single buffer number.
tests/spec/terminal_spec.lua Expanded tests for multi-instance support covering toggling, instance identification, buffer lifecycle, and mode handling.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Neovim
    participant ClaudeCodePlugin
    participant TerminalInstance

    User->>Neovim: Opens tab/project (with git root)
    Neovim->>ClaudeCodePlugin: Calls toggle()
    ClaudeCodePlugin->>ClaudeCodePlugin: Determine instance ID (git root or cwd)
    ClaudeCodePlugin->>ClaudeCodePlugin: Check if instance exists
    alt Instance exists
        ClaudeCodePlugin->>TerminalInstance: Toggle visibility
    else New instance
        ClaudeCodePlugin->>TerminalInstance: Create terminal buffer (named by instance ID)
        ClaudeCodePlugin->>ClaudeCodePlugin: Store buffer in instances table
        ClaudeCodePlugin->>TerminalInstance: Show terminal
    end
Loading

Poem

In tunnels of code, new paths I unspool,
Now each git root gets its own little tool.
Multiple projects, hopping side by side,
Buffers named smartly, with nothing to hide.
With multi-instance magic, my work multiplies—
A rabbit with terminals, wide-open eyes!
🐇✨

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.


Note

⚡️ Faster reviews with caching

CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 30th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.
Enjoy the performance boost—your workflow just got faster.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5dd2196 and 083682e.

📒 Files selected for processing (2)
  • lua/claude-code/keymaps.lua (1 hunks)
  • tests/spec/terminal_spec.lua (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • lua/claude-code/keymaps.lua
  • tests/spec/terminal_spec.lua
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (6)
lua/claude-code/config.lua (1)

174-180: Trim stray whitespace to satisfy Luacheck & maintain code hygiene

Luacheck warns that line 177 contains only whitespace. Keeping the file free of trailing / blank-only lines helps static analysis pass cleanly and avoids noisy diffs later.

-  
+
🧰 Tools
🪛 Luacheck (1.2.0)

[warning] 177-177: line contains only whitespace

(W611)

CLAUDE.md (1)

29-46: Fix markdown-lint issues around heading & code fence spacing

markdownlint flags missing blank lines before the “Multi-Instance Support” heading and around the fenced code block. Adding a single blank line before/after each element resolves MD022 & MD031.

-28
-
-28
-Adding buffer-specific context management
-29# Multi-Instance Support
+28
+Adding buffer-specific context management
+
+29# Multi-Instance Support-38 Buffer names include the git root path for easy identification
-39
-40```lua
+38 Buffer names include the git root path for easy identification
+
+40
+```lua-45 })
-46```
+45 })
+
+46```
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

29-29: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


40-40: Fenced code blocks should be surrounded by blank lines
null

(MD031, blanks-around-fences)

lua/claude-code/init.lua (1)

47-56: Minor guard: ensure instances table exists

get_current_buffer_number() assumes M.claude_code.instances is always a table. If a third-party test swaps M.claude_code this could throw. A tiny guard avoids surprises without overhead.

-  if current_instance then
-    return M.claude_code.instances[current_instance]
+  if current_instance and type(M.claude_code.instances) == 'table' then
+    return M.claude_code.instances[current_instance]
   end
lua/claude-code/terminal.lua (3)

67-78: Nil-safe buffer validity check

vim.api.nvim_buf_is_valid(bufnr) is only called when bufnr == current_bufnr, but an explicit nil check makes intent clearer and avoids accidental API calls with nil in future edits.

-  for _, bufnr in pairs(claude_code.claude_code.instances) do
-    if bufnr == current_bufnr and vim.api.nvim_buf_is_valid(bufnr) then
+  for _, bufnr in pairs(claude_code.claude_code.instances) do
+    if bufnr
+      and bufnr == current_bufnr
+      and vim.api.nvim_buf_is_valid(bufnr)
+    then
🧰 Tools
🪛 Luacheck (1.2.0)

[warning] 68-68: line contains only whitespace

(W611)


[warning] 77-77: line contains only whitespace

(W611)


166-168: Prune invalid buffers to keep instances table clean

When a buffer is wiped (e.g., :bd), its entry remains, forcing a validity check every toggle. Removing stale entries keeps look-ups O(1).

if not vim.api.nvim_buf_is_valid(bufnr) then
  claude_code.claude_code.instances[instance_id] = nil
end

148-148: Remove blank-only lines flagged by Luacheck

Line 148 contains only whitespace (W611). Deleting it silences the linter.

🧰 Tools
🪛 Luacheck (1.2.0)

[warning] 148-148: line contains only whitespace

(W611)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between a021225 and a373afb.

📒 Files selected for processing (4)
  • CLAUDE.md (1 hunks)
  • lua/claude-code/config.lua (3 hunks)
  • lua/claude-code/init.lua (2 hunks)
  • lua/claude-code/terminal.lua (6 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.17.2)
CLAUDE.md

29-29: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


40-40: Fenced code blocks should be surrounded by blank lines
null

(MD031, blanks-around-fences)

🪛 Luacheck (1.2.0)
lua/claude-code/config.lua

[warning] 177-177: line contains only whitespace

(W611)

lua/claude-code/terminal.lua

[warning] 68-68: line contains only whitespace

(W611)


[warning] 77-77: line contains only whitespace

(W611)


[warning] 109-109: line contains only whitespace

(W611)


[warning] 111-111: line contains only whitespace

(W611)


[warning] 148-148: line contains only whitespace

(W611)

🔇 Additional comments (4)
lua/claude-code/config.lua (2)

82-83: 👍 Good default for multi_instance

Adding multi_instance = true by default is sensible and keeps the new feature opt-out rather than opt-in. No issues spotted here.


178-180: Validation block looks correct

The boolean check for git.multi_instance mirrors the pattern used for other fields and should prevent mis-configuration. No changes needed.

lua/claude-code/init.lua (2)

64-66: Great consolidation of buffer lookup

Replacing repeated access to M.claude_code.bufnr with the helper keeps toggle logic agnostic to single vs multi-instance. 👍


88-90: Reuse of helper maintains consistency

Same remark as above—good reuse of get_current_buffer_number().

@ahdzib-maya ahdzib-maya changed the title Add multiple instance per git root Add instance per git root May 20, 2025
@greggh
Copy link
Owner

greggh commented May 21, 2025

Great PR, thank you. Can you go through the nitpicks and refactor suggestions from coderabbit before we merge it?

@ahdzib-maya
Copy link
Contributor Author

Great PR, thank you. Can you go through the nitpicks and refactor suggestions from coderabbit before we merge it?

Sure

- Remove whitespace-only lines throughout codebase
- Add nil-safe buffer validity checks
- Add guard for instances table access
- Add buffer cleanup logic to prune invalid entries
When multi_instance is enabled, now checks use_git_root config:
- If true: uses git root as instance identifier
- If false: uses current working directory as instance identifier

This ensures instance ID respects user configuration preferences.
- Sanitize buffer names to avoid special characters that break :buffer commands
- Update keymaps.lua to use instances table instead of single bufnr
- Update file_refresh.lua to use instances table instead of single bufnr

Note: Tests in terminal_spec.lua still need updating to work with instances table
- Completely rewrite terminal_spec.lua to test instances table instead of single bufnr
- Add comprehensive tests for multi-instance functionality:
  - Instance creation and management
  - Git root vs current directory as instance identifier
  - Buffer name sanitization for special characters
  - Invalid buffer cleanup from instances table
- Add tests for single-instance mode using 'global' key
- Update force_insert_mode tests to work with instances table
- All tests validate both single and multi-instance behavior
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔭 Outside diff range comments (1)
lua/claude-code/keymaps.lua (1)

92-100: ⚠️ Potential issue

Autocommands for only the last instance – clear = true wipes the others

vim.api.nvim_create_augroup('ClaudeCodeTerminalFocus', { clear = true })
is executed every time setup_terminal_navigation() is called (i.e. once per
instance).
Because clear = true empties the existing group, the autocommands that
belong to previously-opened instances are silently removed.
Result: only the most recently initialised terminal keeps the automatic
“re-enter insert mode” behaviour, while earlier instances lose it.

-  local augroup = vim.api.nvim_create_augroup('ClaudeCodeTerminalFocus', { clear = true })
+  -- use one group per buffer/instance or avoid clearing the shared group
+  local augroup = vim.api.nvim_create_augroup(
+    'ClaudeCodeTerminalFocus_' .. buf,   -- unique id
+    { clear = true }
+  )

Alternatively, keep a single group but pass { clear = false } and give each
autocmd a unique desc/id so they don't collide.

🧹 Nitpick comments (3)
tests/spec/terminal_spec.lua (1)

135-139: Trailing whitespace – minor Luacheck warnings

luacheck flags these lines as W611: line contains only whitespace.
While harmless, removing the redundant spaces keeps the test file clean.

Also applies to: 220-220, 247-247

🧰 Tools
🪛 Luacheck (1.2.0)

[warning] 135-135: line contains only whitespace

(W611)

lua/claude-code/terminal.lua (2)

69-80: Consider optimizing the buffer check logic.

The current implementation iterates through all instances to check if the current buffer matches any Claude instance buffer. This works correctly but could be inefficient with many instances. Consider a more direct approach:

-  -- Check if current buffer is any of our Claude instances
-  local is_claude_instance = false
-  for _, bufnr in pairs(claude_code.claude_code.instances) do
-    if bufnr
-      and bufnr == current_bufnr
-      and vim.api.nvim_buf_is_valid(bufnr)
-    then
-      is_claude_instance = true
-      break
-    end
-  end
+  -- Check if current buffer is any of our Claude instances
+  local is_claude_instance = false
+  -- Create a reverse lookup for faster checking
+  local function is_valid_claude_buffer(buf_num)
+    for _, bufnr in pairs(claude_code.claude_code.instances) do
+      if bufnr == buf_num and vim.api.nvim_buf_is_valid(bufnr) then
+        return true
+      end
+    end
+    return false
+  end
+  is_claude_instance = is_valid_claude_buffer(current_bufnr)

This creates a helper function that can be reused if needed elsewhere, making the code more maintainable.


140-142: Consider adding feedback when pruning invalid buffers.

When an invalid buffer is detected, the code correctly removes it from the instances table, but doesn't provide any feedback to the user. Consider adding a notification to help with debugging:

    -- Prune invalid buffer entries
    if bufnr and not vim.api.nvim_buf_is_valid(bufnr) then
      claude_code.claude_code.instances[instance_id] = nil
+     vim.notify("Claude Code: Detected and pruned invalid buffer for instance: " .. instance_id, vim.log.levels.DEBUG)
    end

This would help users understand what's happening if they encounter unexpected behavior.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a373afb and 5dd2196.

📒 Files selected for processing (6)
  • lua/claude-code/config.lua (3 hunks)
  • lua/claude-code/file_refresh.lua (1 hunks)
  • lua/claude-code/init.lua (2 hunks)
  • lua/claude-code/keymaps.lua (1 hunks)
  • lua/claude-code/terminal.lua (6 hunks)
  • tests/spec/terminal_spec.lua (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • lua/claude-code/init.lua
  • lua/claude-code/config.lua
🧰 Additional context used
🪛 Luacheck (1.2.0)
tests/spec/terminal_spec.lua

[warning] 135-135: line contains only whitespace

(W611)


[warning] 220-220: line contains only whitespace

(W611)


[warning] 247-247: line contains only whitespace

(W611)

🔇 Additional comments (7)
lua/claude-code/keymaps.lua (1)

92-95: Nil-safety guard looks fine

Retrieving current_instance and the buffer from the instances table is the
right approach; the subsequent if buf and vim.api.nvim_buf_is_valid(buf) check
prevents errors when no instance is active.
No changes required here.

lua/claude-code/file_refresh.lua (1)

60-63: Buffer resolution aligned with multi-instance design

The timer callback now resolves the buffer through
current_instanceinstances[current_instance].
The nil/validity checks ensure that checktime is only executed when an active
instance buffer is both valid and displayed. Looks good.

lua/claude-code/terminal.lua (5)

11-17: Good refactoring to support multi-instance architecture.

The change from a single buffer reference to a table mapping instance IDs to buffer numbers is well-implemented and properly documented. The new current_instance field provides necessary state tracking.


20-31: Well-designed instance identifier function.

The get_instance_identifier function provides a solid foundation for the multi-instance feature, with appropriate fallback to the current working directory when not in a git repository. The documentation is thorough and clear.


104-116: Good implementation of instance ID logic with proper config respect.

The instance ID determination correctly considers both multi_instance and use_git_root configuration options, addressing the previous review feedback. This gives users flexibility in how instances are managed across different projects.


159-166: Proper buffer name sanitization implemented.

The buffer naming logic now correctly sanitizes the instance ID by replacing non-alphanumeric characters with hyphens, addressing the previous review feedback. This prevents potential issues with spaces and special characters in buffer names.


1-187: Overall excellent implementation of multi-instance support.

The refactoring to support multiple Claude Code instances is well-executed with a clean architecture. The code properly respects configuration options, sanitizes buffer names, and maintains backward compatibility. The changes address all the previous review feedback and introduce a valuable feature that will improve workflows across multiple projects.

- Fix test assertion: use assert.is_nil instead of assert.is_false for pattern matching
- Fix autocommand collision: use unique group names per buffer instance
- Remove trailing whitespace from test file

These changes ensure multi-instance autocommands don't interfere with each other
and fix the test assertion logic for buffer name sanitization validation.
@ahdzib-maya
Copy link
Contributor Author

@greggh anything left to do here?

@greggh
Copy link
Owner

greggh commented May 22, 2025

It looks like I need to do some more cleanup on the CI actions that are failing. I'll get to that this weekend. As for the PR, looks good to me. I'll fix all of these CI issues. Most aren't to do with this PR and have something to do with errors in the lua actions it seems.

Thanks for the awesome PR.

@greggh greggh merged commit 6a79e2b into greggh:main May 22, 2025
2 of 9 checks passed
@greggh greggh mentioned this pull request May 22, 2025
4 tasks
@krishna-bala
Copy link
Collaborator

@ahdzib-maya thank you for the awesome PR 🙏

I'm curious if you thought about/know how this will behave with git worktrees - Anthropic recommends using them for parallel flows on the same project (https://docs.anthropic.com/en/docs/claude-code/tutorials#run-parallel-claude-code-sessions-with-git-worktrees).

It probably comes down to how git.get_git_root() behaves -- does it return the "base" repo for a worktree, does it return the current path for the worktree, or does it return nil?

--- Get the current git root or a fallback identifier
--- @param git table The git module
--- @return string identifier Git root path or fallback identifier
local function get_instance_identifier(git)
  local git_root = git.get_git_root()
  if git_root then
    return git_root
  else
    -- Fallback to current working directory if not in a git repo
    return vim.fn.getcwd()
  end
end

I'll give this a try this weekend and let you know what I find.

@ahdzib-maya
Copy link
Contributor Author

I actually just started using worktrees after seeing that. My guess is it'll work out of the box, but will try it in a bit.

@ahdzib-maya
Copy link
Contributor Author

@ahdzib-maya thank you for the awesome PR 🙏

I'm curious if you thought about/know how this will behave with git worktrees - Anthropic recommends using them for parallel flows on the same project (https://docs.anthropic.com/en/docs/claude-code/tutorials#run-parallel-claude-code-sessions-with-git-worktrees).

It probably comes down to how git.get_git_root() behaves -- does it return the "base" repo for a worktree, does it return the current path for the worktree, or does it return nil?

--- Get the current git root or a fallback identifier
--- @param git table The git module
--- @return string identifier Git root path or fallback identifier
local function get_instance_identifier(git)
  local git_root = git.get_git_root()
  if git_root then
    return git_root
  else
    -- Fallback to current working directory if not in a git repo
    return vim.fn.getcwd()
  end
end

I'll give this a try this weekend and let you know what I find.

Confirmed. Works out of the box.

Little extra note. When working with related repositories in which you need claude to know about them (faster without doing web/fetch) you can sym link, and it works as long as you run the instance on the parent (not the sym link)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants