Hmbown/CodeWhale · error · anyhow::Error
config path must not be a symlink: {}
Error message
config path must not be a symlink: {} What it means
Thrown by reject_path_symlink when the (normalized) config path — the file itself or a parent directory — is a symlink, detected via symlink_metadata so the check does not follow the link. The loader deliberately refuses symlinked config so the exact file that will be read is unambiguous and cannot be swapped by link retargeting.
Source
Thrown at crates/config/src/lib.rs:6743
.read(true)
.custom_flags(libc::O_NOFOLLOW)
.open(path)?;
let mut raw = String::new();
file.read_to_string(&mut raw)?;
Ok(raw)
}
#[cfg(not(unix))]
fn read_string_no_follow(path: &Path) -> std::io::Result<String> {
fs::read_to_string(path)
}
fn reject_path_symlink(path: &Path) -> Result<()> {
let Ok(metadata) = fs::symlink_metadata(path) else {
return Ok(());
};
if metadata.file_type().is_symlink() {
bail!("config path must not be a symlink: {}", path.display());
}
Ok(())
}
#[derive(Debug, Clone, Default)]
struct EnvRuntimeOverrides {
provider: Option<ProviderKind>,
provider_source: Option<&'static str>,
model: Option<String>,
volcengine_model: Option<String>,
wanjie_ark_model: Option<String>,
openrouter_model: Option<String>,
orcarouter_model: Option<String>,
moonshot_model: Option<String>,
xiaomi_mimo_model: Option<String>,
xiaomi_mimo_mode: Option<String>,
novita_model: Option<String>,
fireworks_model: Option<String>,View on GitHub (pinned to 0c42157ee5)
Solutions
- Replace the symlink with the real file (cp the target into place) and edit it directly
- Pass --config pointing at the real target file (absolute, no '..'), bypassing the symlinked location
- Reconfigure your dotfile manager to copy instead of link for this file, or use a bind mount on Linux instead of a symlink
Example fix
# before ln -s ~/shared/codewhale.toml ~/.codewhale/codewhale.toml codewhale # after cp ~/shared/codewhale.toml ~/.codewhale/codewhale.toml codewhale
Defensive patterns
Strategy: validation
Validate before calling
fn is_clean_config_path(p: &std::path::Path) -> bool {
fn sym(v: &std::path::Path) -> bool {
std::fs::symlink_metadata(v).map(|m| m.file_type().is_symlink()).unwrap_or(false)
}
if sym(p) { return false; }
p.ancestors().skip(1).all(|a| !sym(a))
} Try / catch
match normalize_config_file_path(path) {
Ok(p) => p,
Err(err) if err.to_string().contains("must not be a symlink") => {
// resolve the link ourselves and pass the real target instead
let real = std::fs::canonicalize(&path)?;
normalize_config_file_path(real)?
}
Err(err) => return Err(err),
} Prevention
- Install config by copy, not symlink, in dotfile managers (stow --no-symlinks style)
- Check provisioning scripts: home-manager/Nix installs links by default
- Point --config at the real file location directly
When it happens
Trigger: ln -s ~/shared/codewhale.toml ~/.config/codewhale/codewhale.toml; a symlinked parent directory (e.g. ~/.config/codewhale -> /mnt/config); dotfile managers (GNU stow), Nix home-manager, or deploy tooling that installs config as links.
Common situations: Sharing one config across machines via a synced/symlinked file; wanting quick config switching by retargeting a link (this is exactly what is being blocked); container setups that bind config through symlinks.
Related errors
- MCP config path must be a regular file: {}
- refusing non-regular or reparse-point config lock at {}
- config path cannot contain '..' components
- {} is a symbolic link, not a workspace-owned file
- Runtime store file must not be a symlink: {}
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/d74b1cc4ea4b90a8.
Report an issue: GitHub.