From fff576cf98464e05ecf49ae4907dea6119ef8c31 Mon Sep 17 00:00:00 2001 From: Soroush Yousefpour <46920873+gabrimatic@users.noreply.github.com> Date: Wed, 5 Nov 2025 02:44:02 +0100 Subject: [PATCH] fix(core): load custom prompts from symlinked Markdown files (#3643) - Discover prompts via fs::metadata to follow symlinks - Add Unix-only symlink test in custom_prompts.rs - Update docs/prompts.md to mention symlinks Fixes #3637 --------- Signed-off-by: Soroush Yousefpour Co-authored-by: dedrisian-oai Co-authored-by: Eric Traut --- codex-rs/core/src/custom_prompts.rs | 26 ++++++++++++++++++++++---- docs/prompts.md | 2 +- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/codex-rs/core/src/custom_prompts.rs b/codex-rs/core/src/custom_prompts.rs index d92c6839..66b2bab3 100644 --- a/codex-rs/core/src/custom_prompts.rs +++ b/codex-rs/core/src/custom_prompts.rs @@ -32,12 +32,11 @@ pub async fn discover_prompts_in_excluding( while let Ok(Some(entry)) = entries.next_entry().await { let path = entry.path(); - let is_file = entry - .file_type() + let is_file_like = fs::metadata(&path) .await - .map(|ft| ft.is_file()) + .map(|m| m.is_file()) .unwrap_or(false); - if !is_file { + if !is_file_like { continue; } // Only include Markdown files with a .md extension. @@ -197,6 +196,25 @@ mod tests { assert_eq!(names, vec!["good"]); } + #[tokio::test] + #[cfg(unix)] + async fn discovers_symlinked_md_files() { + let tmp = tempdir().expect("create TempDir"); + let dir = tmp.path(); + + // Create a real file + fs::write(dir.join("real.md"), b"real content").unwrap(); + + // Create a symlink to the real file + std::os::unix::fs::symlink(dir.join("real.md"), dir.join("link.md")).unwrap(); + + let found = discover_prompts_in(dir).await; + let names: Vec = found.into_iter().map(|e| e.name).collect(); + + // Both real and link should be discovered, sorted alphabetically + assert_eq!(names, vec!["link", "real"]); + } + #[tokio::test] async fn parses_frontmatter_and_strips_from_body() { let tmp = tempdir().expect("create TempDir"); diff --git a/docs/prompts.md b/docs/prompts.md index 0c2eb3bc..7b47d938 100644 --- a/docs/prompts.md +++ b/docs/prompts.md @@ -5,7 +5,7 @@ Custom prompts turn your repeatable instructions into reusable slash commands, s ### Where prompts live - Location: store prompts in `$CODEX_HOME/prompts/` (defaults to `~/.codex/prompts/`). Set `CODEX_HOME` if you want to use a different folder. -- File type: Codex only loads `.md` files. Non-Markdown files are ignored. +- File type: Codex only loads `.md` files. Non-Markdown files are ignored. Both regular files and symlinks to Markdown files are supported. - Naming: The filename (without `.md`) becomes the prompt name. A file called `review.md` registers the prompt `review`. - Refresh: Prompts are loaded when a session starts. Restart Codex (or start a new session) after adding or editing files. - Conflicts: Files whose names collide with built-in commands (like `init`) stay hidden in the slash popup, but you can still invoke them with `/prompts:`.