Hmbown/CodeWhale · error · io::Error

approval lock has no parent

Error message

approval lock has no parent

What it means

ApprovalReceiptStore::open_lock_file computes the lock file path by joining sessions_dir with the validated session id and asserts the result has a parent component. It fires only in a degenerate case where the joined path unexpectedly has no parent (e.g. an empty sessions_dir root), since the lock file must live inside a directory.

Solutions

  1. Ensure sessions_dir passed to ApprovalReceiptStore::new has at least one parent component; a bare filename or empty path cannot host a per-session lock file.
  2. Use the default_location() constructor, which derives the sessions directory from session_manager::default_sessions_dir().
  3. Create the sessions directory with its proper absolute path before the first append.
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at crates/tui/src/approval_log.rs:200 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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

Appendix: source

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

            ));
        }
        Ok(trimmed)
    }

    pub(crate) fn log_path(&self, session_id: &str) -> io::Result<PathBuf> {
        let session_id = Self::validated_session_id(session_id)?;
        Ok(self.sessions_dir.join(session_id).join(APPROVAL_LOG_FILE))
    }

    fn lock_path(&self, session_id: &str) -> io::Result<PathBuf> {
        let session_id = Self::validated_session_id(session_id)?;
        Ok(self.sessions_dir.join(session_id).join(APPROVAL_LOCK_FILE))
    }

    fn open_lock_file(&self, session_id: &str) -> io::Result<File> {
        let path = self.lock_path(session_id)?;
        let parent = path.parent().ok_or_else(|| {
            io::Error::new(io::ErrorKind::InvalidInput, "approval lock has no parent")
        })?;
        fs::create_dir_all(parent)?;
        OpenOptions::new()
            .create(true)
            .truncate(false)
            .read(true)
            .write(true)
            .open(path)
    }

    fn open_existing_lock_file(&self, session_id: &str) -> io::Result<Option<File>> {
        let path = self.lock_path(session_id)?;
        match OpenOptions::new().read(true).open(path) {
            Ok(file) => Ok(Some(file)),
            Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
            Err(err) => Err(err),
        }
    }

View on GitHub (pinned to 433685b202)