Hmbown/CodeWhale · error · anyhow::Error

failed to remove

Error message

failed to remove {}

What it means

During the public `wipe` operation, the telemetry buffer deletes install-id and state files. If a file exists but `fs::remove_file` fails, the error is captured (first failure wins) and returned with 'failed to remove <path>' context after attempting the remaining deletions.

Solutions

  1. Fix ownership/permissions on the telemetry directory so the current user can delete files within it.
  2. Stop other running instances holding the files open, then retry wipe.
  3. Check for immutable attributes (`lsattr`, `chattr -i`) or Windows file locks.
  4. If a partial wipe is acceptable, remove the reported file manually and re-run wipe.

Example fix

// before
cargo run -- wipe  # as regular user, files owned by root

// after
sudo chown -R $USER ~/.local/share/codewhale/telemetry
cargo run -- wipe
Defensive patterns

Strategy: try-catch

Validate before calling

fn can_delete_dir(dir: &Path) -> bool {
    // delete requires write permission on the directory
    dir.metadata().map(|m| !m.permissions().readonly()).unwrap_or(false)
}

Try / catch

if let Err(e) = telemetry::buffer::wipe(root) {
    if e.to_string().contains("failed to remove") {
        // extract path, chown/stop-holders, then retry once
    }
}

Prevention

When it happens

Trigger: Calling `wipe(root)` when `install_id_path(root)` or `state_path(root)` exists but cannot be deleted: read-only directory, permission denied, file held by another process, or EIO.

Common situations: Running wipe as a non-root user after telemetry files were created by root; files locked by a concurrently running instance; immutable files (chattr +i); read-only volume on Windows due to open handles.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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

Appendix: source

Thrown at crates/telemetry/src/buffer.rs:438

            file.write_all(uuid::Uuid::new_v4().to_string().as_bytes())
                .with_context(|| format!("failed to write {}", tombstone.display()))?;
            file.sync_data()
                .with_context(|| format!("failed to sync {}", tombstone.display()))?;
            drop(file);
        }

        let mut failure: Option<anyhow::Error> = None;
        for path in [buffer_path(root), dryrun_path(root)] {
            if let Err(error) = truncate(&path) {
                failure.get_or_insert(error);
            }
        }
        for path in [install_id_path(root), state_path(root)] {
            if path.exists()
                && let Err(error) = fs::remove_file(&path)
            {
                failure.get_or_insert(
                    anyhow::Error::new(error)
                        .context(format!("failed to remove {}", path.display())),
                );
            }
        }
        match failure {
            Some(error) => Err(error),
            None => Ok(()),
        }
    })
}

/// Clear the tombstone and drop anything buffered before this process was
/// permitted.
///
/// Called by `init` on every arming. Both the exact tombstone generation and a
/// fresh durable permission check must still match while the wipe lock is held.
/// A stale buffer left by an earlier run or by a bug cannot enter the new
/// process's batch.

View on GitHub (pinned to 73e0f67d83)