Hmbown/CodeWhale · error · io::Error

approval log has no parent

Error message

approval log has no parent

What it means

append() resolves the log path and requires a parent directory component before creating it with create_dir_all. Path::parent() returns None only for paths with no directory component (e.g. a bare filename root), so this InvalidInput error indicates the configured base directory for approval logs resolved to something unusable.

Solutions

  1. Check how ApprovalLog's base directory is constructed (home dir / config) and ensure it is an absolute directory path
  2. Pass an explicit valid base directory when constructing ApprovalLog instead of relying on resolution
  3. Inspect log_path() for the given session_id and fix any sanitization that could empty the directory component
  4. Add a startup check that the configured log directory is absolute and non-empty

Example fix

// before
let log = ApprovalLog::new(PathBuf::from(""));

// after
let log = ApprovalLog::new(dirs::home_dir().unwrap().join(".codewhale/approvals"));
Defensive patterns

Strategy: validation

Validate before calling

let base = dirs::home_dir().ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "no home dir"))?;
assert!(base.is_absolute());

Prevention

When it happens

Trigger: log_path(session_id) yields a path whose parent() is None — essentially only when the base directory configuration collapsed to a relative bare filename or an empty/odd root.

Common situations: Misconfigured home/data directory producing an empty base path; test code constructing ApprovalLog with a stub base; unusual session_id handling in log_path.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/39dd4cb71fd6f11a. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/approval_log.rs:275

    pub(crate) fn replay(&self, session_id: &str) -> io::Result<ApprovalReplay> {
        let receipts = self.load(session_id)?;
        ApprovalReplay::from_receipts(&receipts)
            .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))
    }

    pub(crate) fn append(&self, session_id: &str, receipt: &ApprovalReceipt) -> io::Result<()> {
        let lock_file = self.open_lock_file(session_id)?;
        let mut lock = fd_lock::RwLock::new(lock_file);
        let _guard = lock.write()?;
        let path = self.log_path(session_id)?;
        let mut candidate = self.load_unlocked(session_id)?;
        candidate.push(receipt.clone());
        ApprovalReplay::from_receipts(&candidate)
            .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;

        let parent = path.parent().ok_or_else(|| {
            io::Error::new(io::ErrorKind::InvalidInput, "approval log has no parent")
        })?;
        fs::create_dir_all(parent)?;
        let mut line = serde_json::to_vec(receipt)
            .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
        line.push(b'\n');
        let mut file = OpenOptions::new().create(true).append(true).open(&path)?;
        file.write_all(&line)?;
        file.sync_all()?;
        if let Ok(dir) = File::open(parent) {
            let _ = dir.sync_all();
        }
        Ok(())
    }

    #[cfg(test)]
    pub(crate) fn sessions_dir(&self) -> &std::path::Path {
        &self.sessions_dir
    }

View on GitHub (pinned to 433685b202)