Hmbown/CodeWhale · error

Another pet owner is running

Error message

Another pet owner is running

What it means

serve() takes an exclusive advisory file lock (fd_lock::RwLock::try_write) on owner.lock inside the shared habitat root. If another pet_watch owner process already holds it, try_write fails and this error is returned instead of starting a second owner.

Solutions

  1. Find and terminate the running pet owner process before starting another one
  2. Wait for the existing owner to release the lock (exit or finish)
  3. Verify no orphaned owner survives (ps / container list) and clean it up; the lock is held via a live fd, so removing the file alone will not help
Defensive patterns

Strategy: retry

Validate before calling

// Check whether another owner holds the lock before calling serve()
let probe = std::fs::OpenOptions::new().write(true).open(root.join("owner.lock"));
let can_take = probe.map(|f| fd_lock::RwLock::new(f).try_write().is_ok());

Try / catch

match owner::serve(&root) {
    Err(e) if e.to_string().contains("Another pet owner is running") => {
        eprintln!("Owner already running; attaching to it instead.");
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling serve() when another pet owner process is alive and holds owner.lock — e.g. a previous instance still running, a crashed instance whose lock file persists while its process still lives, or launching a second owner from another terminal.

Common situations: Starting the TUI twice in the same workspace; a stale hung owner from a prior session; container restart where the old owner never exited.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/05958e757782b3c4. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/tui/pet_watch/owner.rs:301

// startup line and stop reason are the operator's only output.
#[allow(clippy::print_stdout, clippy::print_stderr)]
pub fn serve(root: PathBuf, requested_port: u16) -> anyhow::Result<()> {
    std::fs::create_dir_all(&root)?;
    #[cfg(unix)]
    {
        use std::os::unix::fs::{MetadataExt, PermissionsExt};
        let metadata = std::fs::symlink_metadata(&root)?;
        if !metadata.is_dir() || metadata.uid() != unsafe { libc::geteuid() } {
            anyhow::bail!("Pet directory must belong to this user and cannot be a link");
        }
        std::fs::set_permissions(&root, std::fs::Permissions::from_mode(0o700))?;
    }
    let lock_path = WorkspaceFile::open(&root, Path::new("owner.lock"), true)?;
    let original = lock_path.open_update(true, false)?;
    let mut lifetime = fd_lock::RwLock::new(original.try_clone()?);
    let _guard = lifetime
        .try_write()
        .map_err(|_| anyhow::anyhow!("Another pet owner is running"))?;
    let mut store = Store::at(&root)?;
    let previous = store.load()?;
    let mut saved = if let Some(text) = previous {
        let value: Saved = serde_json::from_str(&text)?;
        if value.version != 1
            || uuid::Uuid::parse_str(&value.identity).is_err()
            || value.token.len() != 64
            || !value.token.bytes().all(|b| b.is_ascii_hexdigit())
            || !valid_source(&value.source)
            || value.clients.len() > MAX_CLIENTS
            || !value.appearance.valid()
        {
            anyhow::bail!("Invalid shared habitat; the existing file was kept");
        }
        value
    } else {
        Saved {
            version: 1,

View on GitHub (pinned to 73e0f67d83)