Skip to content

[Bug]: Markdown prompts not found when using symlinks #2577

@mmottl

Description

@mmottl

Pre-submission checklist

  • I have read the documentation
  • I have updated the plugin and all dependencies to the latest versions
  • I have searched for existing issues and discussions
  • My issue is not a minor or cosmetic quirk (e.g., formatting, spacing, or other non-functional details)

Neovim version (nvim -v)

NVIM v0.11.5 Build type: Release LuaJIT 2.1.1765228720

Operating system/version

macOS 26.2

Adapter and model

Any

Describe the bug

When specifying prompt library directory locations as documented, e.g.:

require("codecompanion").setup({
  prompt_library = {
    markdown = {
      dirs = {
        vim.fn.getcwd() .. "/.prompts", -- Can be relative
        "~/.dotfiles/.config/prompts", -- Or absolute paths
      },
    },
  }
})

Then CodeCompanion will not find Markdown files in the path if they are symbolic links to the actual files.

Steps to reproduce

  1. Create a valid Markdown prompt file.
  2. Specify a the path to an empty prompt library markdown directory in the configuration.
  3. Create a symbolic link to it in that path that points to the valid Markdown file.
  4. The prompt specified in the Markdown file will not be added to the prompt library and hence cannot be found in the action palette or referenced with a slash.

Expected behavior

Markdown files pointed to by symbolic links should be allowed.

The following diff shows a potential fix:

diff --git a/lua/codecompanion/actions/markdown.lua b/lua/codecompanion/actions/markdown.lua
index 98795204..359dcda8 100644
--- a/lua/codecompanion/actions/markdown.lua
+++ b/lua/codecompanion/actions/markdown.lua
@@ -32,19 +32,21 @@ function M.load_from_dir(dir, context)
   end
 
   while true do
-    local name, type = vim.uv.fs_scandir_next(handle)
+    local name, _ = vim.uv.fs_scandir_next(handle)
     if not name then
       break
     end
 
-    -- Only process .md files
-    if type == "file" and name:match("%.md$") then
+    -- Only process .md files (follow symlinks)
+    if name:match("%.md$") then
       local path = vim.fs.joinpath(dir, name)
-      local ok, prompt = pcall(M.parse_file, path, context)
+      if not file_utils.is_dir(path) then
+        local ok, prompt = pcall(M.parse_file, path, context)
 
-      if ok and prompt then
-        prompt.name = prompt.name or vim.fn.fnamemodify(path, ":t:r")
-        table.insert(prompts, prompt)
+        if ok and prompt then
+          prompt.name = prompt.name or vim.fn.fnamemodify(path, ":t:r")
+          table.insert(prompts, prompt)
+        end
       end
     end
   end

Screenshots or recordings (optional)

No response

minimal.lua file

---@diagnostic disable: missing-fields

--[[
NOTE: Set the config path to enable the copilot adapter to work.
It will search the following paths for a token:
  - "$CODECOMPANION_TOKEN_PATH/github-copilot/hosts.json"
  - "$CODECOMPANION_TOKEN_PATH/github-copilot/apps.json"
--]]
vim.env["CODECOMPANION_TOKEN_PATH"] = vim.fn.expand("~/.config")

vim.env.LAZY_STDPATH = ".repro"
load(vim.fn.system("curl -s https://raw.githubusercontent.com/folke/lazy.nvim/main/bootstrap.lua"))()

-- Your CodeCompanion setup
local plugins = {
  {
    "olimorris/codecompanion.nvim",
    dependencies = {
      { "nvim-lua/plenary.nvim" },
      {
        "nvim-treesitter/nvim-treesitter",
        lazy = false,
        build = ":TSUpdate",
      },

      -- Test with blink.cmp (delete if not required)
      {
        "saghen/blink.cmp",
        lazy = false,
        version = "*",
        opts = {
          keymap = {
            preset = "enter",
            ["<S-Tab>"] = { "select_prev", "fallback" },
            ["<Tab>"] = { "select_next", "fallback" },
          },
          cmdline = { sources = { "cmdline" } },
          sources = {
            default = { "lsp", "path", "buffer", "codecompanion" },
          },
        },
      },

      -- Test with nvim-cmp
      -- { "hrsh7th/nvim-cmp" },
    },
    opts = {
      --Refer to: https://github.com/olimorris/codecompanion.nvim/blob/main/lua/codecompanion/config.lua
      interactions = {
        --NOTE: Change the adapter as required
        chat = { adapter = "copilot" },
        inline = { adapter = "copilot" },
      },
      opts = {
        log_level = "DEBUG",
      },
    },
  },
}

require("lazy.minit").repro({ spec = plugins })

-- CONFIGURE PLUGINS HERE -----------------------------------------------------

-- Setup Tree-sitter
require("nvim-treesitter")
  .install({
    "lua",
    "markdown",
    "markdown_inline",
    "yaml",
  })
  :wait(300000)

-- Setup nvim-cmp
-- local cmp_status, cmp = pcall(require, "cmp")
-- if cmp_status then
--   cmp.setup({
--     mapping = cmp.mapping.preset.insert({
--       ["<C-b>"] = cmp.mapping.scroll_docs(-4),
--       ["<C-f>"] = cmp.mapping.scroll_docs(4),
--       ["<C-Space>"] = cmp.mapping.complete(),
--       ["<C-e>"] = cmp.mapping.abort(),
--       ["<CR>"] = cmp.mapping.confirm({ select = true }),
--       -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items.
--     }),
--   })
-- end

Log output (optional)

No response

Minimal reproduction confirmation

  • Yes, I have tested and provided a minimal.lua file that reproduces the issue

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingreviewed-by-AIThe CodeCompanion agent reviewed this PR

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions