Hmbown/CodeWhale · error · io::Error

Session id ' ' collides with a reserved checkpoint file

Error message

Session id '{}' collides with a reserved checkpoint file

What it means

checkpoint_path validates the session id and then additionally rejects ids whose '<id>.json' equals LEGACY_CHECKPOINT_FILE or OFFLINE_QUEUE_FILE, with 'Session id ... collides with a reserved checkpoint file'. This prevents a per-session checkpoint from overwriting the legacy checkpoint or offline-queue files that live in the same checkpoints/ directory.

Solutions

  1. Pick a session id whose .json name differs from LEGACY_CHECKPOINT_FILE and OFFLINE_QUEUE_FILE
  2. Rename or migrate the foreign/imported session before loading
  3. Add a suffix/prefix to generated ids (e.g. session-<uuid>) so they cannot equal reserved stems

Example fix

// before
let id = "offline-queue"; // collides with OFFLINE_QUEUE_FILE
// after
let id = "offline-queue-session";
Defensive patterns

Strategy: validation

Validate before calling

let name = format!("{}.json", id.trim());
if name == LEGACY_CHECKPOINT_FILE || name == OFFLINE_QUEUE_FILE { return Err(anyhow!("id collides with reserved checkpoint file")); }

Type guard

fn safe_checkpoint_id(id: &str) -> bool {
    let t = id.trim();
    !t.is_empty()
        && t.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
        && format!("{t}.json") != "checkpoint.json"
        && format!("{t}.json") != "offline-queue.json"
}

Try / catch

match manager.checkpoint_path(&id) {
    Ok(p) => use(p),
    Err(e) if e.to_string().contains("reserved checkpoint file") => eprintln!("Session id '{}' uses a reserved file name; rename it", id),
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Calling checkpoint_path (or APIs that derive checkpoint paths from a session id) with an id like the legacy checkpoint stem or the offline-queue stem — e.g. id 'offline-queue' when OFFLINE_QUEUE_FILE is 'offline-queue.json'.

Common situations: Users naming sessions after the reserved stems seen in the checkpoints directory listing; imported or hand-copied session files using legacy names; automated id generation colliding with the legacy file constants.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


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

Appendix: source

Thrown at crates/tui/src/session_manager.rs:1368

            Err(error) => return Err(error),
        };
        if metadata.file_type().is_symlink() || !metadata.is_file() {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!("Session goal {} must be a regular file", path.display()),
            ));
        }
        Ok(true)
    }

    fn validated_checkpoint_path(&self, session_id: &str) -> std::io::Result<PathBuf> {
        let trimmed = self.validated_session_id(session_id)?;
        // Reserved file names inside `checkpoints/` must never collide with a
        // per-session checkpoint file.
        if format!("{trimmed}.json") == LEGACY_CHECKPOINT_FILE
            || format!("{trimmed}.json") == OFFLINE_QUEUE_FILE
        {
            return Err(std::io::Error::new(
                std::io::ErrorKind::InvalidInput,
                format!("Session id '{trimmed}' collides with a reserved checkpoint file"),
            ));
        }
        Ok(self.checkpoints_dir().join(format!("{trimmed}.json")))
    }

    /// Create a new `SessionManager` with the specified sessions directory
    pub fn new(sessions_dir: PathBuf) -> std::io::Result<Self> {
        let sessions_dir = normalize_managed_dir(sessions_dir)?;
        // Ensure the sessions directory exists
        fs::create_dir_all(&sessions_dir)?;
        Ok(Self {
            sessions_dir,
            retention_in_progress: AtomicBool::new(false),
        })
    }

View on GitHub (pinned to 73e0f67d83)