Skip to content

[BUG] [v0.0.7] JsonFileStorage crashes on empty file (serde_json parse error) #154

@climax-dev-1

Description

@climax-dev-1

Description

JsonFileStorage::new in src/cortex-engine/src/memory/store/json_file.rs will panic/return an error when the backing file exists but is empty. This happens because serde_json::from_str("") returns an error for an empty string, unlike the SqliteStorage backend which correctly handles the empty-file case.

Affected File

src/cortex-engine/src/memory/store/json_file.rs, lines 25-39

pub async fn new(path: PathBuf) -> Result<Self> {
    let memories = if path.exists() {
        let content = tokio::fs::read_to_string(&path).await?;
        let list: Vec<Memory> = serde_json::from_str(&content)?;  // <-- crashes if content is empty string
        list.into_iter().map(|m| (m.id, m)).collect()
    } else {
        HashMap::new()
    };
    ...
}

Root Cause

When a JSON memory file is created (e.g., by save() writing an empty []) and then the process is killed before writing, or when the file is created externally as empty, serde_json::from_str("") returns Err(EOF while parsing a value). This propagates as an error from JsonFileStorage::new, preventing the memory system from initializing.

Compare with SqliteStorage::new (lines 28-35) which correctly handles this:

let content = tokio::fs::read_to_string(&path).await.unwrap_or_default();
if content.is_empty() {
    HashMap::new()
} else {
    let list: Vec<Memory> = serde_json::from_str(&content).unwrap_or_default();
    ...
}

Steps to Reproduce

  1. Create an empty file at the path used for JsonFileStorage
  2. Call JsonFileStorage::new(path)
  3. Observe error: EOF while parsing a value at line 1 column 0

Expected Behavior

An empty file should be treated as an empty memory store (no memories), matching the behavior of SqliteStorage.

Actual Behavior

JsonFileStorage::new returns an error, preventing the memory system from initializing.

Fix

let content = tokio::fs::read_to_string(&path).await?;
let list: Vec<Memory> = if content.trim().is_empty() {
    Vec::new()
} else {
    serde_json::from_str(&content)?
};

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions