|
| 1 | +-- Repro fixture for issue #289: |
| 2 | +-- "[BUG] ClaudeCodeSend misclassifies regular buffers as tree buffers when |
| 3 | +-- file path contains 'neo-tree' or 'NvimTree'" |
| 4 | +-- |
| 5 | +-- Root cause (lua/claudecode/init.lua, handle_send_normal + handle_send_visual): |
| 6 | +-- a buffer is classified as a file-explorer ("tree") buffer not only by its |
| 7 | +-- FILETYPE but also by a substring match against its BUFFER NAME: |
| 8 | +-- |
| 9 | +-- local is_tree_buffer = current_ft == "NvimTree" |
| 10 | +-- or current_ft == "neo-tree" |
| 11 | +-- or ... |
| 12 | +-- or string.match(current_bufname, "neo%-tree") -- false positive |
| 13 | +-- or string.match(current_bufname, "NvimTree") -- false positive |
| 14 | +-- or string.match(current_bufname, "minifiles://") |
| 15 | +-- |
| 16 | +-- So a perfectly ordinary file whose PATH happens to contain one of those |
| 17 | +-- substrings (e.g. a Neovim plugin spec at lua/plugins/_neo-tree_.lua, or any |
| 18 | +-- file under a directory called nvim-tree-config/) is mistaken for a tree. |
| 19 | +-- |
| 20 | +-- Symptom, visual path (the README-default `<leader>as` keymap uses |
| 21 | +-- `<cmd>ClaudeCodeSend<cr>`, which KEEPS the buffer in visual mode): |
| 22 | +-- 1. wrapper sees mode == "v" -> exit_visual_and_schedule(visual_handler) |
| 23 | +-- 2. capture_visual_selection_data() returns nil (get_tree_state() is nil for |
| 24 | +-- a real `lua` buffer) and <Esc> is fed, dropping us into normal mode |
| 25 | +-- 3. handle_send_visual: is_tree_buffer == true (bufname match) -> takes the |
| 26 | +-- tree branch -> get_files_from_visual_selection(nil) -> validate_visual_mode() |
| 27 | +-- now fails because the mode is "n" -> logs: |
| 28 | +-- ClaudeCodeSend_visual->TreeAdd: Not in visual mode (current mode: n) |
| 29 | +-- ...and nothing is ever sent to Claude. |
| 30 | +-- |
| 31 | +-- Symptom, normal/range path (`:'<,'>ClaudeCodeSend`): mode is "n", so |
| 32 | +-- handle_send_normal runs; is_tree_buffer is still true, so it calls |
| 33 | +-- integrations.get_selected_files_from_tree(), which fails with: |
| 34 | +-- ClaudeCodeSend->TreeAdd: Not in a supported tree buffer (current filetype: lua) |
| 35 | +-- |
| 36 | +-- A control file whose path has NONE of those substrings works correctly. |
| 37 | +-- |
| 38 | +-- Usage (from repo root): |
| 39 | +-- source fixtures/nvim-aliases.sh |
| 40 | +-- vv issue-289 'lua/plugins/_neo-tree_.lua' # AFFECTED (path has 'neo-tree') |
| 41 | +-- vv issue-289 'lua/regular_plugin.lua' # CONTROL (no substring) |
| 42 | +-- or directly: |
| 43 | +-- NVIM_APPNAME=issue-289 XDG_CONFIG_HOME=fixtures \ |
| 44 | +-- nvim fixtures/issue-289/lua/plugins/_neo-tree_.lua |
| 45 | +-- |
| 46 | +-- Commands provided for automation: |
| 47 | +-- :ReproDump [path] write captured ClaudeCode notifications as JSON to `path` |
| 48 | +-- (defaults to stdpath('cache')/issue289_notifications.json) |
| 49 | +-- :ReproClear clear the captured-notification buffer |
| 50 | + |
| 51 | +local config_dir = vim.fn.stdpath("config") |
| 52 | +local repo_root = vim.fn.fnamemodify(config_dir, ":h:h") |
| 53 | +vim.opt.rtp:prepend(repo_root) |
| 54 | + |
| 55 | +vim.g.mapleader = " " |
| 56 | +vim.g.maplocalleader = "\\" |
| 57 | + |
| 58 | +-- A little extra command height keeps long error notifications from triggering |
| 59 | +-- the blocking hit-enter prompt while driving Neovim with agent-tty. |
| 60 | +vim.o.cmdheight = 3 |
| 61 | +vim.o.more = false |
| 62 | + |
| 63 | +-- Capture every ClaudeCode notification so automation can assert on it without |
| 64 | +-- scraping the screen. We still forward to the original handler so the error is |
| 65 | +-- also visible in a screenshot. |
| 66 | +_G._repro_notifications = {} |
| 67 | +local original_notify = vim.notify |
| 68 | +vim.notify = function(msg, level, opts) |
| 69 | + table.insert(_G._repro_notifications, { |
| 70 | + message = tostring(msg), |
| 71 | + level = level, |
| 72 | + title = opts and opts.title or nil, |
| 73 | + }) |
| 74 | + return original_notify(msg, level, opts) |
| 75 | +end |
| 76 | + |
| 77 | +local ok, claudecode = pcall(require, "claudecode") |
| 78 | +assert(ok, "Failed to load claudecode.nvim from repo root: " .. tostring(claudecode)) |
| 79 | + |
| 80 | +claudecode.setup({ |
| 81 | + auto_start = false, |
| 82 | + -- ERROR notifications fire regardless of log_level; "info" keeps the rest quiet. |
| 83 | + log_level = "info", |
| 84 | + -- No in-editor terminal needed to reproduce the misclassification. |
| 85 | + terminal = { |
| 86 | + provider = "none", |
| 87 | + }, |
| 88 | +}) |
| 89 | + |
| 90 | +-- Best-effort: start the server so the CONTROL path can actually queue an |
| 91 | +-- at-mention. The bug itself does NOT require a running server or a connected |
| 92 | +-- client -- the misclassification happens before any server interaction. |
| 93 | +pcall(function() |
| 94 | + claudecode.start(false) |
| 95 | +end) |
| 96 | + |
| 97 | +-- README-default visual keymap: this is the exact mapping the docs recommend |
| 98 | +-- and the one the reporter uses. `<cmd>...<cr>` PRESERVES visual mode, which is |
| 99 | +-- what routes the request into the (buggy) visual tree-extraction path. |
| 100 | +vim.keymap.set("v", "<leader>as", "<cmd>ClaudeCodeSend<cr>", { desc = "Send to Claude" }) |
| 101 | + |
| 102 | +vim.api.nvim_create_user_command("ReproDump", function(opts) |
| 103 | + local path = opts.args ~= "" and opts.args or (vim.fn.stdpath("cache") .. "/issue289_notifications.json") |
| 104 | + vim.fn.writefile({ vim.json.encode(_G._repro_notifications) }, path) |
| 105 | + original_notify("ReproDump -> " .. path .. " (" .. #_G._repro_notifications .. " notifications)", vim.log.levels.INFO) |
| 106 | +end, { nargs = "?", desc = "Write captured ClaudeCode notifications as JSON" }) |
| 107 | + |
| 108 | +vim.api.nvim_create_user_command("ReproClear", function() |
| 109 | + _G._repro_notifications = {} |
| 110 | +end, { desc = "Clear captured ClaudeCode notifications" }) |
| 111 | + |
| 112 | +-- Diagnostic snapshot: records how the CURRENT buffer would be classified so |
| 113 | +-- automation can assert on the misclassification directly (no screen-scraping). |
| 114 | +vim.api.nvim_create_user_command("ReproState", function(opts) |
| 115 | + local path = opts.args ~= "" and opts.args or (vim.fn.stdpath("cache") .. "/issue289_state.json") |
| 116 | + local buf = 0 |
| 117 | + local ft = vim.bo[buf].filetype |
| 118 | + local bufname = vim.api.nvim_buf_get_name(buf) |
| 119 | + -- `is_tree_buffer` mirrors the plugin's CURRENT predicate (post-#289): |
| 120 | + -- filetype only. On the fixed plugin, running this in `_neo-tree_.lua` reports |
| 121 | + -- is_tree_buffer=false, i.e. the file is correctly treated as a normal buffer. |
| 122 | + local matches_filetype = ft == "NvimTree" or ft == "neo-tree" or ft == "oil" or ft == "minifiles" or ft == "netrw" |
| 123 | + -- Legacy pre-#289 signal: the buffer-NAME substring match that USED to also |
| 124 | + -- flip is_tree_buffer to true (the root cause of #289). Reported for |
| 125 | + -- diagnostics so the fixture still shows why ordinary files misfired before |
| 126 | + -- the fix: legacy_path_substring_match=true while is_tree_buffer=false means |
| 127 | + -- "this file would have been misclassified by the old code". |
| 128 | + local legacy_path_substring_match = (string.match(bufname, "neo%-tree") ~= nil) |
| 129 | + or (string.match(bufname, "NvimTree") ~= nil) |
| 130 | + or (string.match(bufname, "minifiles://") ~= nil) |
| 131 | + local state = { |
| 132 | + filetype = ft, |
| 133 | + bufname = bufname, |
| 134 | + matches_filetype = matches_filetype, |
| 135 | + legacy_path_substring_match = legacy_path_substring_match, |
| 136 | + is_tree_buffer = matches_filetype, |
| 137 | + has_send_command = vim.fn.exists(":ClaudeCodeSend") == 2, |
| 138 | + server_running = (function() |
| 139 | + local ok_cc, cc = pcall(require, "claudecode") |
| 140 | + return ok_cc and cc.state and cc.state.server ~= nil or false |
| 141 | + end)(), |
| 142 | + } |
| 143 | + vim.fn.writefile({ vim.json.encode(state) }, path) |
| 144 | + original_notify("ReproState -> " .. path, vim.log.levels.INFO) |
| 145 | +end, { nargs = "?", desc = "Write current-buffer tree-classification state as JSON" }) |
0 commit comments