{"record":{"id":"7fd50145a0560033","repo":"quickwit-oss/quickwit","slug":"no-parent-directory-for-full-path","errorCode":null,"errorMessage":"no parent directory for {full_path:?}","messagePattern":"no parent directory for (.+?)","errorType":"exception","errorClass":"StorageError","httpStatus":null,"severity":"error","filePath":"quickwit/quickwit-storage/src/local_file_storage.rs","lineNumber":182,"sourceCode":"#[async_trait]\nimpl Storage for LocalFileStorage {\n    async fn check_connectivity(&self) -> anyhow::Result<()> {\n        if !self.root.try_exists()? {\n            // By creating directories, we check if we have the right permissions.\n            tokio::fs::create_dir_all(&self.root).await?\n        }\n        Ok(())\n    }\n\n    #[tracing::instrument(name = \"storage.local_file.put\", level = \"debug\", skip(self, payload), fields(payload_len = payload.len()))]\n    async fn put(\n        &self,\n        path: &Path,\n        payload: Box<dyn crate::PutPayload>,\n    ) -> crate::StorageResult<()> {\n        let full_path = self.full_path(path)?;\n        let parent_dir = full_path.parent().ok_or_else(|| {\n            let err = anyhow::anyhow!(\"no parent directory for {full_path:?}\");\n            StorageErrorKind::Internal.with_error(err)\n        })?;\n\n        tokio::fs::create_dir_all(parent_dir).await?;\n        let mut reader = payload.byte_stream().await?.into_async_read();\n        let named_temp_file = tempfile::NamedTempFile::new_in(parent_dir)?;\n        let (temp_std_file, temp_filepath) = named_temp_file.into_parts();\n        let mut temp_tokio_file = tokio::fs::File::from_std(temp_std_file);\n        tokio::io::copy(&mut reader, &mut temp_tokio_file).await?;\n        temp_tokio_file.flush().await?;\n        temp_tokio_file.sync_data().await?;\n        temp_filepath\n            .persist(&full_path)\n            .map_err(|err| StorageErrorKind::Io.with_error(err))?;\n        // We also need to sync the parent directory to ensure it\n        // the file move has been persisted on all file systems.\n        tokio::fs::File::open(parent_dir).await?.sync_data().await?;\n        Ok(())","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/quickwit-oss/quickwit/blob/a39730c5cdcd1a4fe798403737ae293999ea21f8/quickwit/quickwit-storage/src/local_file_storage.rs#L164-L200","documentation":"LocalFileStorage::put resolves the relative path to a full path and then asks for its parent directory to create it; if full_path has no parent (only possible for degenerate paths like \"/\" itself), it raises StorageErrorKind::Internal. Writing to the root itself is not a valid file operation, so this is treated as an internal invariant failure rather than a user-facing condition.","triggerScenarios":"Calling put with an empty or root-relative path such that full_path resolves to the storage root directory itself (no parent component), e.g. path = \"\" or path = \"/\". Reached from put / bulk puts when the object key is empty.","commonSituations":"An empty object key after a failed string split or join in calling code; a bug dropping the file name from a path before put; misconfigured key templates producing empty keys.","solutions":["Check the path passed to put: it must name a file, not the storage root — fix the key construction so it is non-empty.","Log/inspect the relative key before calling put to catch empty strings produced by upstream parsing or templating.","Add a caller-side guard rejecting empty or root paths before invoking storage.put.","If the empty key comes from a config template, validate the template renders a non-empty file name at startup."],"exampleFix":"// before\nlet key = parts.join(\"\"); // bug: empty separator yields weird/empty keys\nstorage.put(&Path::from(key), payload).await?;\n// after\nlet key = parts.join(\"/\");\nassert!(!key.is_empty(), \"object key must not be empty\");\nstorage.put(&Path::from(&key), payload).await?;","handlingStrategy":"validation","validationCode":"if path.is_empty() || path.as_str() == \"/\" {\n    anyhow::bail!(\"object key must name a file, not the storage root\");\n}","typeGuard":null,"tryCatchPattern":"match storage.put(&path, payload).await {\n    Err(e) if e.kind() == quickwit_storage::StorageErrorKind::Internal\n        && e.to_string().contains(\"no parent directory\") => {\n        anyhow::bail!(\"empty/invalid object key `{}`\", path);\n    }\n    other => other,\n}","preventionTips":["Assert object keys are non-empty and contain a file-name component before put.","Validate key templates render non-empty names at config load time.","Log the relative key just before put to catch upstream parsing bugs early."],"tags":["storage","local-filesystem","invariant-violation","file-write"],"backgroundTag":"file-write-failed","analyzedSha":"a39730c5cdcd1a4fe798403737ae293999ea21f8","analyzedAt":"2026-09-08T13:19:37.784Z","contentChangedAt":"2026-09-08T13:19:37.784Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}