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
- Read the ({e}) detail to identify the storage failure (lock vs disk vs permissions).
- Check free disk space and the store file's write permissions.
- Stop competing processes holding a lock on the store database and retry.
- 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
- Monitor disk space and store row count against META_MAX_ROWS
- Avoid concurrent writers to the store database
- Keep the store on reliable local storage, not overlay/removable mounts
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
- [!] no VERSION constant found in
- [!] no maltrail-sensor package entry found in
- [!] not built - cargo build --release --manifest-path…
- invalid USERS entry ' ' [?] (hint: add whitespace at start…
- invalid configuration
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)