tinyhumansai/openhuman · error
[agent] replay read_run_events_page failed run_id={run_id}:
Error message
[agent] replay read_run_events_page failed run_id={run_id}: {e} What it means
Replay pagination failed while reading the durable event journal: StoreEventJournal::read_from(run_id, offset) errored (replay/ops.rs:67). The page size is already clamped into [1, MAX_EVENTS_LIMIT] before the read, so the failure is storage-level — opening or reading the sqlite-backed journal in the workspace session stores (missing/moved workspace, file lock, corruption, permissions).
Source
Thrown at src/openhuman/agent/tinyagents/replay/ops.rs:67
pub(crate) async fn read_run_events_page(
workspace: &Path,
run_id: &str,
offset: u64,
limit: u64,
) -> anyhow::Result<RunEventsPage> {
// Guard the page size: clamp a zero/absurd limit into [1, MAX].
let effective_limit = limit.clamp(1, MAX_EVENTS_LIMIT);
log::debug!(
"[agent] replay read_run_events_page run_id={run_id} offset={offset} \
limit={limit} effective_limit={effective_limit}"
);
let stores = open_session_stores(workspace);
let journal = StoreEventJournal::new(stores.journal);
// Read one extra record to detect whether a further page exists without a
// second store round-trip.
let mut events = journal.read_from(run_id, offset).await.map_err(|e| {
anyhow::anyhow!("[agent] replay read_run_events_page failed run_id={run_id}: {e}")
})?;
let has_more = events.len() as u64 > effective_limit;
if has_more {
events.truncate(effective_limit as usize);
}
// Offsets are monotonic within a run, so the cursor is simply "one past the
// last returned offset". `None` when this page drained the stream.
let next_offset = if has_more {
events.last().map(|obs| obs.offset + 1)
} else {
None
};
log::debug!(
"[agent] replay read_run_events_page run_id={run_id} returned={} next_offset={:?}",
events.len(),
next_offsetView on GitHub (pinned to a221052e0d)
Solutions
- Verify the workspace path exists and is writable by this process
- Retry after the run that owns the journal finishes writing — sqlite busy locks are transient
- Check disk space and filesystem health; a journal truncated by a crash may need the workspace session store rebuilt
- If two processes share one workspace, stop one — the journal is single-writer
Defensive patterns
Strategy: retry
Validate before calling
// Cheap pre-flight before paging events
if std::fs::metadata(workspace).is_err() {
anyhow::bail!("workspace missing at {} — cannot read run journal", workspace.display());
} Try / catch
// Retry transient sqlite busy locks, give up on real I/O errors
let mut attempt = 0;
loop {
match read_run_events_page(workspace, run_id, offset, limit).await {
Ok(page) => break Ok(page),
Err(e) if attempt < 3 && e.to_string().contains("busy") => {
attempt += 1;
tokio::time::sleep(std::time::Duration::from_millis(200 * attempt)).await;
}
Err(e) => break Err(e),
}
} Prevention
- Page a run's events from the process that owns the workspace whenever possible
- Verify workspace existence and permissions before opening replay queries
- Never point two writer processes at one workspace
When it happens
Trigger: workspace path pointing at a moved/deleted directory; the journal sqlite file locked by a concurrently writing run (busy); corrupted journal db after a crash mid-write; read-only mount or permission change on the workspace.
Common situations: Inspecting a run's events while the run is still writing on a slow disk; restoring a workspace from backup; two core processes pointed at the same OPENHUMAN_WORKSPACE.
Related errors
- [agent] replay read_run_status failed run_id={run_id}: {e}
- [agent] replay list_active_runs failed: {e}
- artifact_list: {e}
- learning_update_facet: {e:#}
- learning_update_facet: upsert failed: {e:#}
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/c3ce767784e08f11.
Report an issue: GitHub.