{"record":{"id":"3d66068e326d9b55","repo":"Hmbown/CodeWhale","slug":"runtime-event-replay-limit-cannot-exceed-max-runt","errorCode":null,"errorMessage":"Runtime event replay_limit cannot exceed {MAX_RUNTIME_EVENT_REPLAY_TAIL}","messagePattern":"Runtime event replay_limit cannot exceed (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":500,"severity":"error","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":6532,"sourceCode":"        thread_id: &str,\n        offset: u64,\n        limit: Option<usize>,\n    ) -> Result<(Vec<RuntimeEventRecord>, u64)> {\n        let store = self.store.clone();\n        let thread_id = thread_id.to_string();\n        tokio::task::spawn_blocking(move || store.events_from_offset(&thread_id, offset, limit))\n            .await\n            .context(\"Runtime event cursor task failed\")?\n    }\n\n    pub(crate) async fn replay_events(\n        &self,\n        thread_id: &str,\n        since_seq: Option<u64>,\n        tail_limit: Option<usize>,\n    ) -> Result<RuntimeEventReplay> {\n        if tail_limit.is_some_and(|limit| limit > MAX_RUNTIME_EVENT_REPLAY_TAIL) {\n            bail!(\"Runtime event replay_limit cannot exceed {MAX_RUNTIME_EVENT_REPLAY_TAIL}\");\n        }\n        let (base_tx, base_rx) = oneshot::channel();\n        let (batch_tx, batches) = mpsc::channel(2);\n        let store = self.store.clone();\n        let thread_id = thread_id.to_string();\n        tokio::task::spawn_blocking(move || {\n            store.publish_event_replay(&thread_id, since_seq, tail_limit, base_tx, batch_tx);\n        });\n        let base_seq = base_rx\n            .await\n            .context(\"Runtime event replay worker ended before initialization\")?\n            .map_err(anyhow::Error::msg)?;\n        Ok(RuntimeEventReplay { base_seq, batches })\n    }\n\n    async fn ensure_engine_loaded(&self, thread_hint: &ThreadRecord) -> Result<EngineHandle> {\n        {\n            let mut active = self.active.lock().await;","sourceCodeStart":6514,"sourceCodeEnd":6550,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/runtime_threads.rs#L6514-L6550","documentation":"replay_events bounds tail_limit by MAX_RUNTIME_EVENT_REPLAY_TAIL (4096, crates/tui/src/runtime_threads.rs:70) so a replay cannot materialize an unbounded event window; any request above the cap is rejected before the replay worker spawns (crates/tui/src/runtime_threads.rs:6532). Use the since_seq cursor for full history instead of a huge tail.","triggerScenarios":"Passing tail_limit = Some(10_000) to replay_events; a client 'load entire history' button requesting all events as a tail; porting an older client that assumed unbounded limits; computing limit as total event count read from elsewhere.","commonSituations":"New UI features requesting big windows; scripts copying a store's event count into replay_limit; version upgrades that introduced the cap after clients were written.","solutions":["Clamp tail_limit to 4096 (limit.min(MAX_RUNTIME_EVENT_REPLAY_TAIL)) before calling","Page through history with since_seq cursors instead of one large tail","Request only the window you actually render","Compute limit from viewport size, not from total event count"],"exampleFix":"// before\nlet replay = runtime.replay_events(&thread_id, None, Some(total_event_count)).await?;\n\n// after\nconst MAX_TAIL: usize = 4096;\nlet tail = Some(total_event_count.min(MAX_TAIL));\nlet replay = runtime.replay_events(&thread_id, since_seq, tail).await?;","handlingStrategy":"validation","validationCode":"// Clamp before calling.\nconst MAX_RUNTIME_EVENT_REPLAY_TAIL: usize = 4096;\nlet tail_limit = tail_limit.map(|l| l.min(MAX_RUNTIME_EVENT_REPLAY_TAIL));\nlet replay = runtime.replay_events(thread_id, since_seq, tail_limit).await?;","typeGuard":"fn is_valid_tail_limit(limit: Option<usize>) -> bool {\n    limit.is_none_or(|l| l <= 4096)\n}","tryCatchPattern":"match runtime.replay_events(thread_id, since_seq, tail_limit).await {\n    Ok(replay) => { /* ... */ }\n    Err(err) if err.to_string().contains(\"replay_limit cannot exceed\") => {\n        let clamped = tail_limit.map(|l| l.min(4096));\n        runtime.replay_events(thread_id, since_seq, clamped).await?;\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["Never derive replay_limit from total event counts; derive it from the viewport","Page full history with the since_seq cursor instead of one large tail","Centralize the 4096 cap as a shared constant clients import","Add an integration test asserting oversized requests are clamped, not passed through"],"tags":["rust","events","replay","pagination","validation"],"backgroundTag":"pagination-limit-exceeded","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}