From 05de9d366aff627f044e0a40daf5fcbcf277ea30 Mon Sep 17 00:00:00 2001 From: "Christian C. Berclaz" Date: Mon, 25 May 2026 15:34:31 +0200 Subject: [PATCH 1/4] fix(init): respect CLAUDE_CONFIG_DIR for global paths rtk init -g ignored CLAUDE_CONFIG_DIR and always targeted ~/.claude. Honor the env var Claude Code uses to relocate its config directory. Priority: RTK_CLAUDE_DIR > CLAUDE_CONFIG_DIR > $HOME/.claude. Empty values are treated as unset. Manual-step instructions now print the resolved path instead of a hardcoded ~/.claude. All callsites that previously hardcoded home.join(CLAUDE_DIR) now use the shared resolve_claude_dir(): hook_check (rtk gain), integrity (rtk verify), telemetry, and discover (rtk discover). Closes #633 --- src/core/telemetry.rs | 7 +++- src/discover/provider.rs | 12 ++---- src/hooks/hook_check.rs | 27 +++++-------- src/hooks/init.rs | 84 +++++++++++++++++++++++++++++++++++++--- src/hooks/integrity.rs | 19 ++++----- 5 files changed, 105 insertions(+), 44 deletions(-) diff --git a/src/core/telemetry.rs b/src/core/telemetry.rs index 426c1e1fc..f239629eb 100644 --- a/src/core/telemetry.rs +++ b/src/core/telemetry.rs @@ -353,10 +353,13 @@ fn detect_hook_type() -> String { None => return "unknown".to_string(), }; + let claude_dir = + crate::hooks::init::resolve_claude_dir().unwrap_or_else(|_| home.join(".claude")); + // Check in order of popularity let checks = [ - (home.join(".claude/hooks/rtk-rewrite.sh"), "claude"), - (home.join(".claude/hooks/rtk-rewrite.json"), "claude"), + (claude_dir.join("hooks/rtk-rewrite.sh"), "claude"), + (claude_dir.join("hooks/rtk-rewrite.json"), "claude"), (home.join(".gemini/hooks/rtk-hook.sh"), "gemini"), (home.join(".codex/AGENTS.md"), "codex"), (home.join(".cursor/hooks/rtk-rewrite.json"), "cursor"), diff --git a/src/discover/provider.rs b/src/discover/provider.rs index a9b630a2b..9554a8b56 100644 --- a/src/discover/provider.rs +++ b/src/discover/provider.rs @@ -1,6 +1,6 @@ //! Reads Claude Code session logs from disk and streams their command history. -use crate::hooks::constants::CLAUDE_DIR; +use crate::hooks::init::resolve_claude_dir; use anyhow::{Context, Result}; use std::collections::HashMap; use std::fs; @@ -44,12 +44,8 @@ pub struct ClaudeProvider; impl ClaudeProvider { /// Get the base directory for Claude Code projects. fn projects_dir() -> Result { - let home = dirs::home_dir().context("could not determine home directory")?; - Ok(Self::projects_dir_for_home(&home)) - } - - fn projects_dir_for_home(home: &Path) -> PathBuf { - home.join(CLAUDE_DIR).join("projects") + let claude_dir = resolve_claude_dir()?; + Ok(claude_dir.join("projects")) } fn discover_sessions_in_projects_dir( @@ -435,7 +431,7 @@ mod tests { #[test] fn test_discover_sessions_missing_projects_dir_returns_empty() { let temp_home = tempfile::tempdir().unwrap(); - let missing_projects_dir = temp_home.path().join(CLAUDE_DIR).join("projects"); + let missing_projects_dir = temp_home.path().join(".claude").join("projects"); let sessions = ClaudeProvider::discover_sessions_in_projects_dir( &missing_projects_dir, diff --git a/src/hooks/hook_check.rs b/src/hooks/hook_check.rs index 4dd26d82b..454e034c8 100644 --- a/src/hooks/hook_check.rs +++ b/src/hooks/hook_check.rs @@ -1,9 +1,9 @@ //! Detects whether RTK hooks are installed and warns if they are outdated. use super::constants::{ - CLAUDE_DIR, CLAUDE_HOOK_COMMAND, HOOKS_SUBDIR, PRE_TOOL_USE_KEY, REWRITE_HOOK_FILE, - SETTINGS_JSON, + CLAUDE_HOOK_COMMAND, HOOKS_SUBDIR, PRE_TOOL_USE_KEY, REWRITE_HOOK_FILE, SETTINGS_JSON, }; +use super::init::resolve_claude_dir; use crate::core::constants::RTK_DATA_DIR; use std::path::PathBuf; @@ -25,23 +25,17 @@ pub enum HookStatus { /// Returns `Ok` if no Claude Code is detected (not applicable). pub fn status() -> HookStatus { // Don't warn users who don't have Claude Code installed - let home = match dirs::home_dir() { - Some(h) => h, - None => return HookStatus::Ok, + let claude_dir = match resolve_claude_dir() { + Ok(d) => d, + Err(_) => return HookStatus::Ok, }; - let claude_dir = home.join(CLAUDE_DIR); if !claude_dir.exists() { return HookStatus::Ok; } - // Check for new binary command in settings.json first + // Binary hook command takes precedence — if registered, hook is up to date + // regardless of whether the legacy script file is still present. if binary_hook_registered(&claude_dir) { - // If old script file still exists alongside new command, report Outdated - // (migration not complete — user should run `rtk init -g` to clean up) - let old_hook = claude_dir.join(HOOKS_SUBDIR).join(REWRITE_HOOK_FILE); - if old_hook.exists() { - return HookStatus::Outdated; - } return HookStatus::Ok; } @@ -134,11 +128,8 @@ pub fn parse_hook_version(content: &str) -> u8 { } fn hook_installed_path() -> Option { - let home = dirs::home_dir()?; - let path = home - .join(CLAUDE_DIR) - .join(HOOKS_SUBDIR) - .join(REWRITE_HOOK_FILE); + let claude_dir = resolve_claude_dir().ok()?; + let path = claude_dir.join(HOOKS_SUBDIR).join(REWRITE_HOOK_FILE); if path.exists() { Some(path) } else { diff --git a/src/hooks/init.rs b/src/hooks/init.rs index 4c68ec49b..cb4aba6be 100644 --- a/src/hooks/init.rs +++ b/src/hooks/init.rs @@ -507,7 +507,10 @@ fn prompt_telemetry_consent() -> Result<()> { } fn print_manual_instructions(hook_command: &str, include_opencode: bool) { - println!("\n MANUAL STEP: Add this to ~/.claude/settings.json:"); + let settings_path = resolve_claude_dir() + .map(|dir| dir.join(SETTINGS_JSON)) + .unwrap_or_else(|_| PathBuf::from("~/.claude/settings.json")); + println!("\n MANUAL STEP: Add this to {}:", settings_path.display()); println!(" {{"); println!(" \"hooks\": {{ \"PreToolUse\": [{{"); println!(" \"matcher\": \"Bash\","); @@ -2718,11 +2721,28 @@ fn resolve_home_subdir(subdir: &str) -> Result { }) } -fn resolve_claude_dir() -> Result { - if let Ok(dir) = std::env::var("RTK_CLAUDE_DIR") { - return Ok(PathBuf::from(dir)); +pub fn resolve_claude_dir() -> Result { + resolve_claude_dir_from( + std::env::var_os("RTK_CLAUDE_DIR").map(PathBuf::from), + std::env::var_os("CLAUDE_CONFIG_DIR").map(PathBuf::from), + dirs::home_dir(), + ) +} + +fn resolve_claude_dir_from( + rtk_override: Option, + claude_config_dir: Option, + home_dir: Option, +) -> Result { + if let Some(path) = rtk_override.filter(|p| !p.as_os_str().is_empty()) { + return Ok(path); + } + if let Some(path) = claude_config_dir.filter(|p| !p.as_os_str().is_empty()) { + return Ok(path); } - resolve_home_subdir(CLAUDE_DIR) + home_dir + .map(|h| h.join(CLAUDE_DIR)) + .context("Cannot determine Claude config directory. Set $CLAUDE_CONFIG_DIR or $HOME.") } fn resolve_codex_dir() -> Result { @@ -5007,6 +5027,60 @@ mod tests { assert_eq!(missing_falls_back, home_dir.join(".codex")); } + #[test] + fn test_resolve_claude_dir_prefers_rtk_override() { + let result = resolve_claude_dir_from( + Some(PathBuf::from("/custom/rtk-claude")), + Some(PathBuf::from("/custom/claude-config")), + Some(PathBuf::from("/home/user")), + ) + .unwrap(); + assert_eq!(result, PathBuf::from("/custom/rtk-claude")); + } + + #[test] + fn test_resolve_claude_dir_uses_claude_config_dir() { + let result = resolve_claude_dir_from( + None, + Some(PathBuf::from("/custom/claude-config")), + Some(PathBuf::from("/home/user")), + ) + .unwrap(); + assert_eq!(result, PathBuf::from("/custom/claude-config")); + } + + #[test] + fn test_resolve_claude_dir_falls_back_to_home() { + let result = + resolve_claude_dir_from(None, None, Some(PathBuf::from("/home/user"))).unwrap(); + assert_eq!(result, PathBuf::from("/home/user/.claude")); + } + + #[test] + fn test_resolve_claude_dir_ignores_empty_overrides() { + let empty_rtk = resolve_claude_dir_from( + Some(PathBuf::new()), + None, + Some(PathBuf::from("/home/user")), + ) + .unwrap(); + assert_eq!(empty_rtk, PathBuf::from("/home/user/.claude")); + + let empty_claude = resolve_claude_dir_from( + None, + Some(PathBuf::new()), + Some(PathBuf::from("/home/user")), + ) + .unwrap(); + assert_eq!(empty_claude, PathBuf::from("/home/user/.claude")); + } + + #[test] + fn test_resolve_claude_dir_errors_without_home() { + let err = resolve_claude_dir_from(None, None, None).unwrap_err(); + assert!(err.to_string().contains("Cannot determine Claude config")); + } + #[test] fn test_resolve_hermes_home_prefers_hermes_home() { let hermes_home = OsString::from("~/custom hermes home"); diff --git a/src/hooks/integrity.rs b/src/hooks/integrity.rs index 1101fe26a..0bae9d5cb 100644 --- a/src/hooks/integrity.rs +++ b/src/hooks/integrity.rs @@ -12,7 +12,8 @@ //! //! Reference: SA-2025-RTK-001 (Finding F-01) -use super::constants::{CLAUDE_DIR, HOOKS_SUBDIR, REWRITE_HOOK_FILE}; +use super::constants::{HOOKS_SUBDIR, REWRITE_HOOK_FILE}; +use super::init::resolve_claude_dir; use anyhow::{Context, Result}; use sha2::{Digest, Sha256}; use std::fs; @@ -186,15 +187,11 @@ fn read_stored_hash(path: &Path) -> Result { Ok(hash.to_string()) } -/// Resolve the default hook path (~/.claude/hooks/rtk-rewrite.sh) +/// Resolve the default hook path (e.g. ~/.claude/hooks/rtk-rewrite.sh) +/// Respects CLAUDE_CONFIG_DIR and RTK_CLAUDE_DIR overrides. pub fn resolve_hook_path() -> Result { - dirs::home_dir() - .map(|h| { - h.join(CLAUDE_DIR) - .join(HOOKS_SUBDIR) - .join(REWRITE_HOOK_FILE) - }) - .context("Cannot determine home directory. Is $HOME set?") + let claude_dir = resolve_claude_dir()?; + Ok(claude_dir.join(HOOKS_SUBDIR).join(REWRITE_HOOK_FILE)) } /// Run integrity check and print results (for `rtk verify` subcommand) @@ -210,8 +207,8 @@ pub fn run_verify(verbose: u8) -> Result<()> { // If no legacy script exists, check for native binary command registration if !hook_path.exists() && !hash_file.exists() { // Check if the native binary command is registered in settings.json - let home = dirs::home_dir().context("Cannot determine home directory")?; - let settings_path = home.join(CLAUDE_DIR).join("settings.json"); + let claude_dir = resolve_claude_dir()?; + let settings_path = claude_dir.join("settings.json"); if settings_path.exists() { let content = fs::read_to_string(&settings_path).unwrap_or_default(); if content.contains("rtk hook claude") { From 9671d9ea7e89f403855278c5348c5729729dbf84 Mon Sep 17 00:00:00 2001 From: "Christian C. Berclaz" Date: Mon, 8 Jun 2026 18:15:58 +0200 Subject: [PATCH 2/4] refactor(init): address review feedback on CLAUDE_CONFIG_DIR PR - restore migration Outdated check in hook_check (binary registered + legacy script present still reports Outdated until cleanup) - add error context to resolve_claude_dir call sites (provider, integrity) - use CLAUDE_DIR const instead of literal in telemetry + provider test - simplify settings_path construction in print_manual_instructions Refs #2087 --- src/core/telemetry.rs | 4 ++-- src/discover/provider.rs | 7 +++++-- src/hooks/hook_check.rs | 9 +++++++-- src/hooks/init.rs | 4 ++-- src/hooks/integrity.rs | 2 +- 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/core/telemetry.rs b/src/core/telemetry.rs index f239629eb..857e5d163 100644 --- a/src/core/telemetry.rs +++ b/src/core/telemetry.rs @@ -353,8 +353,8 @@ fn detect_hook_type() -> String { None => return "unknown".to_string(), }; - let claude_dir = - crate::hooks::init::resolve_claude_dir().unwrap_or_else(|_| home.join(".claude")); + let claude_dir = crate::hooks::init::resolve_claude_dir() + .unwrap_or_else(|_| home.join(crate::hooks::constants::CLAUDE_DIR)); // Check in order of popularity let checks = [ diff --git a/src/discover/provider.rs b/src/discover/provider.rs index 9554a8b56..d8f73c6fa 100644 --- a/src/discover/provider.rs +++ b/src/discover/provider.rs @@ -44,7 +44,7 @@ pub struct ClaudeProvider; impl ClaudeProvider { /// Get the base directory for Claude Code projects. fn projects_dir() -> Result { - let claude_dir = resolve_claude_dir()?; + let claude_dir = resolve_claude_dir().context("could not determine claude directory")?; Ok(claude_dir.join("projects")) } @@ -431,7 +431,10 @@ mod tests { #[test] fn test_discover_sessions_missing_projects_dir_returns_empty() { let temp_home = tempfile::tempdir().unwrap(); - let missing_projects_dir = temp_home.path().join(".claude").join("projects"); + let missing_projects_dir = temp_home + .path() + .join(crate::hooks::constants::CLAUDE_DIR) + .join("projects"); let sessions = ClaudeProvider::discover_sessions_in_projects_dir( &missing_projects_dir, diff --git a/src/hooks/hook_check.rs b/src/hooks/hook_check.rs index 454e034c8..ca6faf517 100644 --- a/src/hooks/hook_check.rs +++ b/src/hooks/hook_check.rs @@ -33,9 +33,14 @@ pub fn status() -> HookStatus { return HookStatus::Ok; } - // Binary hook command takes precedence — if registered, hook is up to date - // regardless of whether the legacy script file is still present. + // Check for new binary command in settings.json first if binary_hook_registered(&claude_dir) { + // If old script file still exists alongside new command, report Outdated + // (migration not complete — user should run `rtk init -g` to clean up) + let old_hook = claude_dir.join(HOOKS_SUBDIR).join(REWRITE_HOOK_FILE); + if old_hook.exists() { + return HookStatus::Outdated; + } return HookStatus::Ok; } diff --git a/src/hooks/init.rs b/src/hooks/init.rs index cb4aba6be..b070ada8a 100644 --- a/src/hooks/init.rs +++ b/src/hooks/init.rs @@ -508,8 +508,8 @@ fn prompt_telemetry_consent() -> Result<()> { fn print_manual_instructions(hook_command: &str, include_opencode: bool) { let settings_path = resolve_claude_dir() - .map(|dir| dir.join(SETTINGS_JSON)) - .unwrap_or_else(|_| PathBuf::from("~/.claude/settings.json")); + .unwrap_or_else(|_| PathBuf::from(CLAUDE_DIR)) + .join(SETTINGS_JSON); println!("\n MANUAL STEP: Add this to {}:", settings_path.display()); println!(" {{"); println!(" \"hooks\": {{ \"PreToolUse\": [{{"); diff --git a/src/hooks/integrity.rs b/src/hooks/integrity.rs index 0bae9d5cb..e5c160154 100644 --- a/src/hooks/integrity.rs +++ b/src/hooks/integrity.rs @@ -207,7 +207,7 @@ pub fn run_verify(verbose: u8) -> Result<()> { // If no legacy script exists, check for native binary command registration if !hook_path.exists() && !hash_file.exists() { // Check if the native binary command is registered in settings.json - let claude_dir = resolve_claude_dir()?; + let claude_dir = resolve_claude_dir().context("Cannot determine claude directory")?; let settings_path = claude_dir.join("settings.json"); if settings_path.exists() { let content = fs::read_to_string(&settings_path).unwrap_or_default(); From c59a76375a5516e364e44d88d4c5936ae180af17 Mon Sep 17 00:00:00 2001 From: Nicolas Le Cam Date: Tue, 9 Jun 2026 00:36:26 +0200 Subject: [PATCH 3/4] review: cleanup code, comments and uneeded undocumented RTK_CLAUDE_DIR now that we have CLAUDE_CONFIG_DIR --- hooks/hermes/tests/test_rtk_rewrite_plugin.py | 2 +- src/core/telemetry.rs | 5 ++- src/hooks/init.rs | 43 ++++++------------- src/hooks/integrity.rs | 6 +-- 4 files changed, 18 insertions(+), 38 deletions(-) diff --git a/hooks/hermes/tests/test_rtk_rewrite_plugin.py b/hooks/hermes/tests/test_rtk_rewrite_plugin.py index 143426246..b940a243c 100644 --- a/hooks/hermes/tests/test_rtk_rewrite_plugin.py +++ b/hooks/hermes/tests/test_rtk_rewrite_plugin.py @@ -313,7 +313,7 @@ def test_cargo_init_installs_importable_plugin_that_rewrites_with_fake_rtk(self) env["RUSTUP_HOME"] = str(real_home / ".rustup") if "CARGO_HOME" not in env and (real_home / ".cargo").exists(): env["CARGO_HOME"] = str(real_home / ".cargo") - env.pop("RTK_CLAUDE_DIR", None) + env.pop("CLAUDE_CONFIG_DIR", None) result = subprocess.run( ["cargo", "run", "--quiet", "--", "init", "--agent", "hermes"], diff --git a/src/core/telemetry.rs b/src/core/telemetry.rs index 857e5d163..d9bee51f0 100644 --- a/src/core/telemetry.rs +++ b/src/core/telemetry.rs @@ -3,6 +3,8 @@ use super::constants::RTK_DATA_DIR; use crate::core::config; use crate::core::tracking; +use crate::hooks::constants::CLAUDE_DIR; +use crate::hooks::init::resolve_claude_dir; use sha2::{Digest, Sha256}; use std::fmt::Write as FmtWrite; use std::io::Write as IoWrite; @@ -353,8 +355,7 @@ fn detect_hook_type() -> String { None => return "unknown".to_string(), }; - let claude_dir = crate::hooks::init::resolve_claude_dir() - .unwrap_or_else(|_| home.join(crate::hooks::constants::CLAUDE_DIR)); + let claude_dir = resolve_claude_dir().unwrap_or_else(|_| home.join(CLAUDE_DIR)); // Check in order of popularity let checks = [ diff --git a/src/hooks/init.rs b/src/hooks/init.rs index b070ada8a..bad6ca5d2 100644 --- a/src/hooks/init.rs +++ b/src/hooks/init.rs @@ -2723,21 +2723,16 @@ fn resolve_home_subdir(subdir: &str) -> Result { pub fn resolve_claude_dir() -> Result { resolve_claude_dir_from( - std::env::var_os("RTK_CLAUDE_DIR").map(PathBuf::from), std::env::var_os("CLAUDE_CONFIG_DIR").map(PathBuf::from), dirs::home_dir(), ) } fn resolve_claude_dir_from( - rtk_override: Option, - claude_config_dir: Option, + claude_dir: Option, home_dir: Option, ) -> Result { - if let Some(path) = rtk_override.filter(|p| !p.as_os_str().is_empty()) { - return Ok(path); - } - if let Some(path) = claude_config_dir.filter(|p| !p.as_os_str().is_empty()) { + if let Some(path) = claude_dir.filter(|path| !path.as_os_str().is_empty()) { return Ok(path); } home_dir @@ -5031,7 +5026,6 @@ mod tests { fn test_resolve_claude_dir_prefers_rtk_override() { let result = resolve_claude_dir_from( Some(PathBuf::from("/custom/rtk-claude")), - Some(PathBuf::from("/custom/claude-config")), Some(PathBuf::from("/home/user")), ) .unwrap(); @@ -5041,7 +5035,6 @@ mod tests { #[test] fn test_resolve_claude_dir_uses_claude_config_dir() { let result = resolve_claude_dir_from( - None, Some(PathBuf::from("/custom/claude-config")), Some(PathBuf::from("/home/user")), ) @@ -5051,33 +5044,21 @@ mod tests { #[test] fn test_resolve_claude_dir_falls_back_to_home() { - let result = - resolve_claude_dir_from(None, None, Some(PathBuf::from("/home/user"))).unwrap(); + let result = resolve_claude_dir_from(None, Some(PathBuf::from("/home/user"))).unwrap(); assert_eq!(result, PathBuf::from("/home/user/.claude")); } #[test] fn test_resolve_claude_dir_ignores_empty_overrides() { - let empty_rtk = resolve_claude_dir_from( - Some(PathBuf::new()), - None, - Some(PathBuf::from("/home/user")), - ) - .unwrap(); - assert_eq!(empty_rtk, PathBuf::from("/home/user/.claude")); - - let empty_claude = resolve_claude_dir_from( - None, - Some(PathBuf::new()), - Some(PathBuf::from("/home/user")), - ) - .unwrap(); - assert_eq!(empty_claude, PathBuf::from("/home/user/.claude")); + let empty = + resolve_claude_dir_from(Some(PathBuf::new()), Some(PathBuf::from("/home/user"))) + .unwrap(); + assert_eq!(empty, PathBuf::from("/home/user/.claude")); } #[test] fn test_resolve_claude_dir_errors_without_home() { - let err = resolve_claude_dir_from(None, None, None).unwrap_err(); + let err = resolve_claude_dir_from(None, None).unwrap_err(); assert!(err.to_string().contains("Cannot determine Claude config")); } @@ -5893,12 +5874,12 @@ mod tests { let claude_dir = tmp.path().join(CLAUDE_DIR); fs::create_dir_all(&claude_dir).unwrap(); - let orig = std::env::var_os("RTK_CLAUDE_DIR"); - std::env::set_var("RTK_CLAUDE_DIR", &claude_dir); + let orig = std::env::var_os("CLAUDE_CONFIG_DIR"); + std::env::set_var("CLAUDE_CONFIG_DIR", &claude_dir); f(&claude_dir); match orig { - Some(v) => std::env::set_var("RTK_CLAUDE_DIR", v), - None => std::env::remove_var("RTK_CLAUDE_DIR"), + Some(v) => std::env::set_var("CLAUDE_CONFIG_DIR", v), + None => std::env::remove_var("CLAUDE_CONFIG_DIR"), } } diff --git a/src/hooks/integrity.rs b/src/hooks/integrity.rs index e5c160154..fc991dc06 100644 --- a/src/hooks/integrity.rs +++ b/src/hooks/integrity.rs @@ -187,11 +187,9 @@ fn read_stored_hash(path: &Path) -> Result { Ok(hash.to_string()) } -/// Resolve the default hook path (e.g. ~/.claude/hooks/rtk-rewrite.sh) -/// Respects CLAUDE_CONFIG_DIR and RTK_CLAUDE_DIR overrides. +/// Resolve the default hook path (~/.claude/hooks/rtk-rewrite.sh) pub fn resolve_hook_path() -> Result { - let claude_dir = resolve_claude_dir()?; - Ok(claude_dir.join(HOOKS_SUBDIR).join(REWRITE_HOOK_FILE)) + resolve_claude_dir().map(|dir| dir.join(HOOKS_SUBDIR).join(REWRITE_HOOK_FILE)) } /// Run integrity check and print results (for `rtk verify` subcommand) From 6785a6c7695d7273e722214a295249a84819b6f0 Mon Sep 17 00:00:00 2001 From: Nicolas Le Cam Date: Tue, 9 Jun 2026 00:43:55 +0200 Subject: [PATCH 4/4] fix: minor print_manual_instructions regression --- src/hooks/init.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/init.rs b/src/hooks/init.rs index bad6ca5d2..28363f4ce 100644 --- a/src/hooks/init.rs +++ b/src/hooks/init.rs @@ -508,7 +508,7 @@ fn prompt_telemetry_consent() -> Result<()> { fn print_manual_instructions(hook_command: &str, include_opencode: bool) { let settings_path = resolve_claude_dir() - .unwrap_or_else(|_| PathBuf::from(CLAUDE_DIR)) + .unwrap_or_else(|_| PathBuf::from(format!("~/{}", CLAUDE_DIR))) .join(SETTINGS_JSON); println!("\n MANUAL STEP: Add this to {}:", settings_path.display()); println!(" {{");