{"record":{"id":"37d505ca01366098","repo":"rathole-org/rathole","slug":"config-file-should-have-a-parent-dir","errorCode":null,"errorMessage":"config file should have a parent dir","messagePattern":"config file should have a parent dir","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/config_watcher.rs","lineNumber":148,"sourceCode":"    let _ = shutdown_rx.recv().await;\n    Ok(())\n}\n\n#[cfg(feature = \"notify\")]\n#[instrument(skip(shutdown_rx, event_tx, old))]\nasync fn config_watcher(\n    path: PathBuf,\n    mut shutdown_rx: broadcast::Receiver<bool>,\n    event_tx: mpsc::UnboundedSender<ConfigChange>,\n    mut old: Config,\n) -> Result<()> {\n    let (fevent_tx, mut fevent_rx) = mpsc::unbounded_channel();\n    let path = if path.is_absolute() {\n        path\n    } else {\n        env::current_dir()?.join(path)\n    };\n    let parent_path = path.parent().expect(\"config file should have a parent dir\");\n    let path_clone = path.clone();\n    let mut watcher =\n        notify::recommended_watcher(move |res: Result<notify::Event, _>| match res {\n            Ok(e) => {\n                if matches!(e.kind, EventKind::Modify(_))\n                    && e.paths\n                        .iter()\n                        .map(|x| x.file_name())\n                        .any(|x| x == path_clone.file_name())\n                {\n                    let _ = fevent_tx.send(true);\n                }\n            }\n            Err(e) => error!(\"watch error: {:#}\", e),\n        })?;\n\n    watcher.watch(parent_path, RecursiveMode::NonRecursive)?;\n    info!(\"Start watching the config\");","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/rathole-org/rathole/blob/a292f7ed5402f840415fc6a53827da2f34337856/src/config_watcher.rs#L130-L166","documentation":"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.","triggerScenarios":"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.","commonSituations":"Passing an empty --config argument; environment variable expansion that produced an empty string (e.g. CONFIG_PATH unset); constructing the path programmatically without validation.","solutions":["Pass a valid config file path including its directory (e.g. /etc/fantastic/config.toml or ./config.toml).","Check the environment variable or flag feeding the path isn't empty.","Make the path absolute with a directory component before calling config_watcher.","Default to a known-good config path when the user-supplied path is blank."],"exampleFix":"// before\nlet path = std::env::var(\"CONFIG\").unwrap(); // may be \"\"\n// after\nlet path = std::env::var(\"CONFIG\").unwrap_or_else(|_| \"./config.toml\".to_string());","handlingStrategy":"validation","validationCode":"let path = std::path::PathBuf::from(config_arg);\nif path.as_os_str().is_empty() || path.parent().is_none() {\n    anyhow::bail!(\"config path must be a non-empty file path with a parent directory\");\n}","typeGuard":"fn has_parent(p: &std::path::Path) -> bool {\n    !p.as_os_str().is_empty() && p.parent().map(|x| !x.as_os_str().is_empty()).unwrap_or(false)\n}","tryCatchPattern":null,"preventionTips":["Never pass an empty/blank config path; default it early","Expand env vars and check for empty results","Use absolute paths for config files"],"tags":["panic","config","filesystem","watcher"],"backgroundTag":"internal-invariant-violation","analyzedSha":"a292f7ed5402f840415fc6a53827da2f34337856","analyzedAt":"2026-09-07T09:56:55.739Z","contentChangedAt":"2026-09-07T09:56:55.739Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}