← All needs

My Work page blocks the worker event loop for seconds to minutes on a large database (1 s poll + full rebuild on every new observation)

### Summary With the My Work page open, the worker stops answering `/api/health`, `/api/search` and hook calls for 7–30 s at a time, and the watchdog/hooks treat it as dead. On my machine that meant 9 worker recycles in one hour, `/api/search` timing out after 45 s in other sessions, and the page itself failing with "connection refused" while the worker restarted. ### Environment - claude-mem 15.2.1 (Claude Code plugin), Windows 11 26300, Bun runtime from the plugin - Database 2.85 GB: 143,945 observations, 193,759 tool_uses, 9,822 session summaries, 2,840 sdk_sessions, 676 work_state_entries - 4 Claude Code sessions writing observations in parallel ### Measurements - One `GET /api/work-view` (3.3 MB response): 7–13 s; a `/api/health` call issued during it waited 7.3 s (normally 0.005 s). Once, with a second client connected, it ran past 120 s. - With only an SSE client on `/api/work-view/events` and nothing else: health probes stalled for 30 s at a time, repeatedly. - `POST /api/work-view/rules` with `action: "approve"` on an existing rule blocked the worker until it was recycled (86 s). The same route with an unknown `ruleId` returns 404 in 0.08 s, so the cost is the state rebuild, not the request. - All SQL that the work view issues is fast when run on its own (each query ≤0.2 s, the `E3e` activity signature about 0.6 s in total), so the time goes to the JS rebuild and how often it runs. ### Cause (from the 15.2.1 bundle) 1. `events` starts `setInterval(() => this.publish(), this.pollIntervalMs)` with `pollIntervalMs` = 1000 while any SSE client is connected. 2. Each `publish()` calls the work-view source's `read()`, which computes the activity signature `E3e` (MAX(id) + COUNT(*) over observations, session_summaries, tool_uses, user_prompts, sdk_sessions) synchronously on the main thread. 3. Whenever that signature moved (every new observation, i.e. every few seconds with several live sessions), `read()` rebuilds the whole agent view (`aae(...)` + `b3e(...)`) and the server pushes the full ~3.3 MB snapshot to every client. So with an open tab and active sessions the worker spends most of its time rebuilding the work view. The scheduled My Work refresh has the same effect at startup: "Work view refresh started (scheduled)" runs before `Binding worker HTTP server`, and while it gathers input and applies results the worker does not answer health. I turned it off (`CLAUDE_MEM_WORK_VIEW_AUTO_REFRESH=false`) for this reason. ### Local workaround that fixed it In `read()`, recompute the activity signature at most once per 120 s and reuse the previous one inside that window; work-state entry changes (rules, to-dos) still trigger an immediate rebuild because their own COUNT/MAX signature is checked separately. With an SSE client connected for 2.5 minutes: 85 health probes, median 7 ms, none above 0.2 s. The first connection after a worker start still takes ~60 s to build. ### Suggested fixes - Build the work view off the event loop (worker thread / child process), or at least yield between steps. - Coalesce rebuilds: debounce activity-driven rebuilds (seconds to minutes), and stop polling every 1000 ms; push deltas or a revision number instead of the full 3.3 MB snapshot on every change. - Keep `/api/health` answerable during work-view builds, so a busy worker is not mistaken for a dead one. - Make the rules routes (`approve`, `edit`, ...) persist the entry and return without waiting for a full rebuild. Related: #5090 (build story hits the 12,000-char cap; seen here too).