affaan-m/ECC · warning · anyhow::Error

Context graph entity name cannot be empty

Error message

Context graph entity name cannot be empty

What it means

Raised by upsert_context_entity in ecc2/src/session/store.rs:3103 when name.trim().is_empty(). The name, together with entity_type and optional path, forms the entity_key via context_graph_entity_key; an empty name would produce ambiguous keys and break lookups, so it is rejected before insert.

Source

Thrown at ecc2/src/session/store.rs:3103

        Ok(stats)
    }

    pub fn upsert_context_entity(
        &self,
        session_id: Option<&str>,
        entity_type: &str,
        name: &str,
        path: Option<&str>,
        summary: &str,
        metadata: &BTreeMap<String, String>,
    ) -> Result<ContextGraphEntity> {
        let entity_type = entity_type.trim();
        if entity_type.is_empty() {
            return Err(anyhow::anyhow!("Context graph entity type cannot be empty"));
        }
        let name = name.trim();
        if name.is_empty() {
            return Err(anyhow::anyhow!("Context graph entity name cannot be empty"));
        }

        let normalized_path = path.map(str::trim).filter(|value| !value.is_empty());
        let summary = summary.trim();
        let entity_key = context_graph_entity_key(entity_type, name, normalized_path);
        let metadata_json = serde_json::to_string(metadata)
            .context("Failed to serialize context graph metadata")?;
        let timestamp = chrono::Utc::now().to_rfc3339();

        self.conn.execute(
            "INSERT INTO context_graph_entities (
                session_id, entity_key, entity_type, name, path, summary, metadata_json, created_at, updated_at
             )
             VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?8)
             ON CONFLICT(entity_key) DO UPDATE SET
                session_id = COALESCE(excluded.session_id, context_graph_entities.session_id),
                summary = CASE
                    WHEN excluded.summary <> '' THEN excluded.summary

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Validate name at the call site: require non-empty after trim; if name is derived from a path, fall back to the path string or skip the record.
  2. During import, drop or quarantine rows with empty names.
  3. Co-locate validation so upsert_context_entity never receives empty names.

Example fix

// before
let name = path.file_name().map(|s| s.to_string_lossy().to_string()).unwrap_or_default();
store.upsert_context_entity(None, etype, &name, Some(&path), summary, &meta)?;

// after: guard the derived name
let name = path.file_name()
    .map(|s| s.to_string_lossy().to_string())
    .unwrap_or_else(|| path.to_string_lossy().to_string());
if name.trim().is_empty() {
    anyhow::bail!("cannot derive a non-empty entity name from {path}");
}
store.upsert_context_entity(None, etype, &name, Some(&path), summary, &meta)?;
Defensive patterns

Strategy: validation

Validate before calling

// Same newtype pattern for the name.
#[derive(Debug, Clone)]
pub struct ContextEntityName(String);

impl ContextEntityName {
    pub fn new(raw: &str) -> anyhow::Result<Self> {
        let trimmed = raw.trim();
        if trimmed.is_empty() {
            anyhow::bail!("context graph entity name cannot be empty");
        }
        Ok(Self(trimmed.to_string()))
    }
    pub fn as_str(&self) -> &str { &self.0 }
}

// When deriving name from a path, fall back to the path string itself.
fn name_from_path(path: &Path) -> anyhow::Result<ContextEntityName> {
    let raw = path.file_name()
        .map(|s| s.to_string_lossy().to_string())
        .unwrap_or_else(|| path.to_string_lossy().to_string());
    ContextEntityName::new(&raw)
}

Type guard

// ContextEntityName (above) is the type guard: it cannot represent an
// empty string.

Try / catch

for record in import_stream {
    let name = match ContextEntityName::new(&record.name) {
        Ok(n) => n,
        Err(_) => { tracing::warn!("skipping entity with empty name"); continue; }
    };
    store.upsert_context_entity(sid, etype, name.as_str(), path, summary, &meta)?;
}

Prevention

When it happens

Trigger: Passing an empty or whitespace-only name; a caller that builds name from a filename whose basename is empty; importing entities where the name column is blank.

Common situations: Bulk import with sparse data; deriving name from Path::file_name() on a path ending in '/' (returns None -> ""); UI input not validated.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/95a4c32a62fba1ed. Report an issue: GitHub.