screenpipe/screenpipe · error

cannot determine parent dir of {:?}

Error message

cannot determine parent dir of {:?}

What it means

atomic_write writes pipe files via a temp file plus atomic rename/persist. It needs the target's parent directory to place the temp file; if path.parent() is None (path has no directory component, e.g. a bare relative filename), it throws this error.

Source

Thrown at crates/screenpipe-core/src/pipes/mod.rs:7272

        .find("\n---")
        .ok_or_else(|| anyhow!("could not find closing --- in front-matter"))?;

    let yaml_str = &rest[..end];
    let body = rest[end + 4..].trim().to_string();

    let config: PipeConfig = serde_yaml::from_str(yaml_str)?;

    Ok((config, body))
}

/// Atomic file write: write to a temp file in the same directory, then rename.
/// On Unix, rename is atomic. On Windows, this avoids the partial-write window
/// where a concurrent reader (e.g. the scheduler) sees a truncated file.
fn atomic_write(path: &Path, content: &str) -> Result<()> {
    use std::io::Write;
    let dir = path
        .parent()
        .ok_or_else(|| anyhow!("cannot determine parent dir of {:?}", path))?;
    let mut tmp = tempfile::NamedTempFile::new_in(dir)?;
    tmp.write_all(content.as_bytes())?;
    tmp.flush()?;
    // persist atomically (rename on Unix, MoveFileEx on Windows)
    tmp.persist(path)?;
    Ok(())
}

/// Serialize a PipeConfig + body back to pipe.md format.
/// Name is excluded from frontmatter (derived from directory name).
pub fn serialize_pipe(config: &PipeConfig, body: &str) -> Result<String> {
    let mut cfg = config.clone();
    cfg.name = String::new(); // empty → skip_serializing_if kicks in

    // Remove legacy "config" key from extras — old pipe.md files had a nested
    // `config: { enabled: true }` block that gets captured by the flattened
    // HashMap and re-emitted forever. Also strip any keys that shadow real
    // struct fields to prevent duplicates.

View on GitHub (pinned to 4ebf712990)

Solutions

  1. Join the filename onto an absolute directory before writing: PathBuf::from(pipe_dir).join("pipe.md").
  2. Call canonicalize() (or std::env::current_dir().join(...)) on relative paths before passing them.
  3. Pass paths derived from the pipe's install directory rather than bare names.

Example fix

// before
atomic_write(Path::new("pipe.md"), content)?;
// after
let path = pipe_dir.join("pipe.md");
atomic_write(&path, content)?;
Defensive patterns

Strategy: validation

Validate before calling

const path = require('path');
const p = path.resolve(targetPath); // ensures a directory component exists
if (path.dirname(p) === p) throw new Error('path has no parent directory: ' + p);

Type guard

fn has_parent(path: &Path) -> bool {
    path.parent().is_some()
}

Try / catch

match atomic_write(&path, content) {
    Err(e) if e.to_string().starts_with("cannot determine parent dir") => {
        eprintln!("pass a path with a directory component, e.g. dir.join(\"pipe.md\")");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling a pipe-writing operation that ends in atomic_write with a Path like "pipe.md" (no directory prefix) or a root-less path constructed from components only.

Common situations: Programmatically building paths as Path::new("file.md") instead of joining a directory; passing a filename from config without resolving it against the pipe directory.

Related errors


AI-assisted analysis of screenpipe/screenpipe@4ebf712990 (2026-09-01). Data as JSON: /api/errors/c18b8fd47fc45ca6. Report an issue: GitHub.