stamparm/maltrail · error

condensed observable store: prune failed

Error message

condensed observable store: prune failed ({e})

What it means

The sensor's condensed observable store maintenance failed during its prune step. prune_condensed_store deletes the lowest-value rows to keep the store within META_MAX_ROWS; on Err(e) it logs "condensed observable store: prune failed ({e})" with log_error. Unpruned rows can grow the store unboundedly across cycles.

Solutions

  1. Read the ({e}) detail to identify the storage failure (lock vs disk vs permissions).
  2. Check free disk space and the store file's write permissions.
  3. Stop competing processes holding a lock on the store database and retry.
  4. Back up or recreate the condensed store file if it is corrupted.
Defensive patterns

Strategy: try-catch

Validate before calling

// preflight storage before prune
let meta = std::fs::metadata(&store_path)?;
assert!(!read_only(&meta.permissions()), "store file must be writable");
// ensure disk headroom
let free = free_disk_bytes(store_dir)?;
assert!(free > 64 * 1024 * 1024, "insufficient disk for prune");

Type guard

fn store_writable(path: &Path) -> bool {
    path.is_file() && std::fs::OpenOptions::new().append(true).open(path).is_ok()
}

Try / catch

if let Err(e) = prune_condensed_store(&store) {
    log_prune_failure(&e);
    alert_ops("condensed store prune failed; storage growth possible");
}

Prevention

When it happens

Trigger: The DELETE/prune SQL or storage operation against the condensed observable store returns Err(e): database file locked, disk full, I/O error, or corrupted store database.

Common situations: Disk exhaustion on the sensor host preventing writes; SQLite-style 'database is locked' from concurrent readers/writers; permission problems on the store file after a user change; filesystem errors on removable/overlay storage in containers.

Related errors


AI-assisted analysis of stamparm/maltrail@77cfb06d76 (2026-09-13). Data as JSON: /api/errors/0a8c45bf7c5e8ab8. Report an issue: GitHub.

Appendix: source

Thrown at sensor/src/main.rs:974

/// need a line per hour saying nothing was over budget. A failure IS reported, because a store
/// that cannot be pruned will grow without bound.
fn prune_condensed_store(cfg: &Config) {
    if !cfg.use_condensed_storage {
        return;
    }
    let path = maltrail_sensor::meta::meta_db_path(&cfg.log_dir);
    match maltrail_sensor::meta::prune(&path, maltrail_sensor::settings::META_MAX_ROWS) {
        Ok(0) => {}
        Ok(deleted) => {
            if !cfg.quiet {
                cprintln!(
                    "[i] condensed observable store: pruned {deleted} lowest-value rows to the \
                     {} row budget",
                    maltrail_sensor::settings::META_MAX_ROWS
                );
            }
        }
        Err(e) => output::log_error(&format!("condensed observable store: prune failed ({e})"), true),
    }
}

/// One trail-update cycle plus the reporting around it (`sensor.py:init():update_timer()`).
///
/// `startup` distinguishes the synchronous refresh before the first load from the periodic one.
/// A failure is never fatal: the sensor continues with whatever trails it already has, but says
/// so loudly, because running on stale trails means silently missing detections.
fn refresh_trails(cfg: &Config, quiet: bool, startup: bool) {
    match trailupdate::run(cfg) {
        trailupdate::Outcome::Updated => {
            if !quiet && !startup {
                cprintln!("[i] trails updated");
            }
        }
        trailupdate::Outcome::Disabled => {
            if !quiet && startup {
                cprintln!(

View on GitHub (pinned to 77cfb06d76)