feat: evict old terminal runs to cap history at 50#80
Conversation
fe381a3 to
d24f90c
Compare
Backend: replace all naive datetime.now() with datetime.now(timezone.utc) and datetime.fromtimestamp(..., tz=timezone.utc) so serialized ISO strings include +00:00 suffix. Frontend toLocaleTimeString() handles UTC→local conversion automatically. Frontend: reset stateEvents and activeNodes when __start__ fires at the beginning of each chat turn, so the graph highlights start fresh instead of retaining completed status from prior turns. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
RunService.runs grows indefinitely, leaking memory on long-running servers. Add eviction logic that removes oldest completed/failed runs when total exceeds MAX_RUNS (50), preserving active runs. Clean up WebSocket subscriptions for evicted runs via on_run_removed callback. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
d24f90c to
9e0ce67
Compare
There was a problem hiding this comment.
Pull request overview
This pull request implements memory management for the UiPath Developer Console by capping the run history at 50 entries and making several related improvements to execution tracking.
Changes:
- Adds eviction logic that removes oldest terminal (completed/failed) runs when total exceeds 50, while preserving all active runs
- Implements timezone-aware datetime handling throughout the codebase using
timezone.utc - Refactors frontend execution tracking to use event-log-based state derivation for more robust multi-node execution visualization
- Adds convenience features: JSON copy button for trace trees and GitHub link in sidebar
Reviewed changes
Copilot reviewed 21 out of 23 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/uipath/dev/services/run_service.py |
Adds _evict_old_runs() method and on_run_removed callback support; timezone-aware datetime usage |
src/uipath/dev/server/ws/manager.py |
Adds remove_run_subscriptions() to clean up WebSocket subscriptions for evicted runs |
src/uipath/dev/server/__init__.py |
Wires on_run_removed callback to connection manager |
src/uipath/dev/models/execution.py |
Updates datetime usage to be timezone-aware |
src/uipath/dev/models/messages.py |
Updates datetime usage to be timezone-aware |
src/uipath/dev/models/data.py |
Updates datetime usage to be timezone-aware |
src/uipath/dev/models/chat.py |
Updates datetime usage to be timezone-aware |
src/uipath/dev/infrastructure/tracing_exporter.py |
Updates datetime.fromtimestamp to include timezone |
src/uipath/dev/infrastructure/logging_handlers.py |
Updates datetime.fromtimestamp to include timezone |
src/uipath/dev/server/frontend/src/store/useRunStore.ts |
Refactors activeNodes to track executing nodes dict; adds removeActiveNode and resetRunGraphState |
src/uipath/dev/server/frontend/src/store/useWebSocket.ts |
Adds support for removeActiveNode and resetRunGraphState on state events |
src/uipath/dev/server/frontend/src/components/graph/GraphPanel.tsx |
Derives execution state from event log for consistent multi-node tracking |
src/uipath/dev/server/frontend/src/components/traces/TraceTree.tsx |
Adds JSON copy button for trace tree |
src/uipath/dev/server/frontend/src/components/layout/Sidebar.tsx |
Adds GitHub repository link |
src/uipath/dev/server/frontend/src/App.tsx |
Replays state events when loading historical runs |
pyproject.toml, uv.lock |
Version bump to 0.0.55 |
src/uipath/dev/server/static/* |
Generated frontend build artifacts |
Comments suppressed due to low confidence (1)
src/uipath/dev/services/run_service.py:156
- The sorting key uses
datetime.min.replace(tzinfo=timezone.utc)as a fallback for runs without anend_time. However, this means runs that are still pending/running/suspended (which shouldn't be in the terminal list anyway) would sort to the very beginning if they somehow ended up there. Consider adding an assertion or explicit check that all runs in the terminal list have anend_time, or usedatetime.maxinstead to ensure any missing end_time sorts to the end (most recent) and won't be evicted.
terminal.sort(
key=lambda r: r.end_time or datetime.min.replace(tzinfo=timezone.utc),
reverse=True,
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| """Register a new run and emit an initial update.""" | ||
| self.runs[run.id] = run | ||
| self._emit_run_updated(run) | ||
| self._evict_old_runs() |
There was a problem hiding this comment.
The eviction logic should be triggered after a run completes or fails, not just after registration. Currently, eviction only happens in register_run, but runs that transition from "running" to "completed"/"failed" won't trigger eviction. Consider calling _evict_old_runs() at the end of the execute method after setting the run status to "completed" or "failed".
Summary
RunService.runsdict grows indefinitely — eachExecutionRunholds unbounded lists of traces, logs, states, and chat events, leaking memory on long-running serversRunServicethat triggers after each new run is registered, removing oldest terminal (completed/failed) runs when total exceedsMAX_RUNS(50)on_run_removedcallback wired toConnectionManager.remove_run_subscriptionsTest plan
RunService.runsuv run ruff check src/ tests/,uv run ruff format --check .,uv run mypy src/— all pass🤖 Generated with Claude Code