Skip to content

Comments

feat: evict old terminal runs to cap history at 50#80

Merged
cristipufu merged 4 commits intomainfrom
feat/run-history-eviction
Feb 21, 2026
Merged

feat: evict old terminal runs to cap history at 50#80
cristipufu merged 4 commits intomainfrom
feat/run-history-eviction

Conversation

@cristipufu
Copy link
Member

Summary

  • RunService.runs dict grows indefinitely — each ExecutionRun holds unbounded lists of traces, logs, states, and chat events, leaking memory on long-running servers
  • Add eviction logic in RunService that triggers after each new run is registered, removing oldest terminal (completed/failed) runs when total exceeds MAX_RUNS (50)
  • Active runs (pending/running/suspended) are never evicted
  • WebSocket subscriptions for evicted runs are cleaned up via a new on_run_removed callback wired to ConnectionManager.remove_run_subscriptions

Test plan

  • Start server, trigger >50 runs, verify oldest completed runs are evicted from RunService.runs
  • Verify active (running/suspended) runs are preserved even when total exceeds 50
  • Verify WebSocket subscriptions are cleaned up for evicted runs
  • Run uv run ruff check src/ tests/, uv run ruff format --check ., uv run mypy src/ — all pass

🤖 Generated with Claude Code

@cristipufu cristipufu force-pushed the feat/run-history-eviction branch from fe381a3 to d24f90c Compare February 21, 2026 09:30
cristipufu and others added 4 commits February 21, 2026 11:32
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>
@cristipufu cristipufu force-pushed the feat/run-history-eviction branch from d24f90c to 9e0ce67 Compare February 21, 2026 09:32
@cristipufu cristipufu requested a review from Copilot February 21, 2026 09:33
@cristipufu cristipufu self-assigned this Feb 21, 2026
@cristipufu cristipufu merged commit 0df6662 into main Feb 21, 2026
13 checks passed
@cristipufu cristipufu deleted the feat/run-history-eviction branch February 21, 2026 09:34
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 an end_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 an end_time, or use datetime.max instead 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()
Copy link

Copilot AI Feb 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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".

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant