rathole-org/rathole · error

config file should have a parent dir

Error message

config file should have a parent dir

What it means

config_watcher needs the config file's parent directory to register filesystem notifications. After resolving relative paths against the current directory, the code calls path.parent() and `expect`s it to succeed; a Path without a parent (e.g. an empty or root-only path) panics with this message at src/config_watcher.rs:148.

Solutions

  1. Pass a valid config file path including its directory (e.g. /etc/fantastic/config.toml or ./config.toml).
  2. Check the environment variable or flag feeding the path isn't empty.
  3. Make the path absolute with a directory component before calling config_watcher.
  4. Default to a known-good config path when the user-supplied path is blank.

Example fix

// before
let path = std::env::var("CONFIG").unwrap(); // may be ""
// after
let path = std::env::var("CONFIG").unwrap_or_else(|_| "./config.toml".to_string());
Defensive patterns

Strategy: validation

Validate before calling

let path = std::path::PathBuf::from(config_arg);
if path.as_os_str().is_empty() || path.parent().is_none() {
    anyhow::bail!("config path must be a non-empty file path with a parent directory");
}

Type guard

fn has_parent(p: &std::path::Path) -> bool {
    !p.as_os_str().is_empty() && p.parent().map(|x| !x.as_os_str().is_empty()).unwrap_or(false)
}

Prevention

When it happens

Trigger: Calling config_watcher with a path that has no parent directory component — e.g. an empty string path, or a path that normalizes to root — so `path.parent()` returns None.

Common situations: Passing an empty --config argument; environment variable expansion that produced an empty string (e.g. CONFIG_PATH unset); constructing the path programmatically without validation.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of rathole-org/rathole@a292f7ed54 (2026-09-07). Data as JSON: /api/errors/37d505ca01366098. Report an issue: GitHub.

Appendix: source

Thrown at src/config_watcher.rs:148

    let _ = shutdown_rx.recv().await;
    Ok(())
}

#[cfg(feature = "notify")]
#[instrument(skip(shutdown_rx, event_tx, old))]
async fn config_watcher(
    path: PathBuf,
    mut shutdown_rx: broadcast::Receiver<bool>,
    event_tx: mpsc::UnboundedSender<ConfigChange>,
    mut old: Config,
) -> Result<()> {
    let (fevent_tx, mut fevent_rx) = mpsc::unbounded_channel();
    let path = if path.is_absolute() {
        path
    } else {
        env::current_dir()?.join(path)
    };
    let parent_path = path.parent().expect("config file should have a parent dir");
    let path_clone = path.clone();
    let mut watcher =
        notify::recommended_watcher(move |res: Result<notify::Event, _>| match res {
            Ok(e) => {
                if matches!(e.kind, EventKind::Modify(_))
                    && e.paths
                        .iter()
                        .map(|x| x.file_name())
                        .any(|x| x == path_clone.file_name())
                {
                    let _ = fevent_tx.send(true);
                }
            }
            Err(e) => error!("watch error: {:#}", e),
        })?;

    watcher.watch(parent_path, RecursiveMode::NonRecursive)?;
    info!("Start watching the config");

View on GitHub (pinned to a292f7ed54)