Skip to content

fix(dashboard): stop live session-thread counts inflating on first-session requests - #608

Merged
SantiagoDePolonia merged 2 commits into
mainfrom
fix/live-thread-count
Jul 28, 2026
Merged

fix(dashboard): stop live session-thread counts inflating on first-session requests#608
SantiagoDePolonia merged 2 commits into
mainfrom
fix/live-thread-count

Conversation

@SantiagoDePolonia

@SantiagoDePolonia SantiagoDePolonia commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Problem

With "Group by session" enabled and the Audit Logs page open, sending the first messages of a fresh session made the thread count badge (audit-thread-count) read 2x–3x the real number of requests. Refreshing the page fixed it, and later messages appended to an existing session counted correctly.

Root cause

audit.started live events are published before session detection stamps the request context, so every live row is first inserted sessionless and only later events carry the session_id. When a later event made two on-screen rows collapse into one thread (or a new live request displaced a fetched head), the demoted row was dropped if the thread's children list had never been lazily fetched — prependLiveAuditThreadChild was a no-op for unloaded threads.

Every subsequent live event for that dropped request (updated, stream chunks, completed, flushed) then matched nothing on screen, so foldLiveAuditIntoThread re-counted it as a brand-new session member — +1 on the badge per event. The bug only shows on a session's first live-rendered messages because that is the only time several rows of one session are live with events still in flight.

Fix

  • prependLiveAuditThreadChild now always retains the displaced entry: for never-expanded threads it creates a partial children list marked loaded: false. Late events find their entry and merge in place instead of re-counting.
  • toggleThread treats partial lists as unfetched (checks loaded/loading rather than slot existence), so expanding still fetches the full session page.
  • fetchThreadEntries keeps live-displaced entries visible while the page loads and merges them on top of the fetched page via the new mergeAuditThreadChildren helper (deduped by id/request_id, including against a head replaced mid-flight; still-unpersisted live entries are preserved and counted into the thread total). Failed/stale fetches keep the partial entries instead of deleting the slot.

Refactor (while there)

mergeLiveAuditEntry carried a near-complete duplicate of its merge flow for audit.detail events; it is now a single flow with an isDetail flag (detail events still never re-trigger the detail fetch, and never fold), with lifecycle-state stamping extracted into liveAuditPatch. auditThreadChildEntries grew into mergeAuditThreadChildren and the old helper was removed.

User-visible impact

The thread badge now equals the number of distinct requests in the session while watching live traffic. Transient approximations remain only in genuinely unknowable cases (e.g. events replayed after a thread scrolled off the page) and reconcile on the next /admin/audit/sessions refetch, which was always correct.

Tests

  • New regression test replaying the exact reported sequence (two interleaved requests, sessionless audit.started, late session ids, completed/flushed for both): badge stays at 2 — the old code produced 4.
  • New tests for displaced-entry retention and the fetch-merge helper; the test asserting the old dropping behavior was rewritten to the new invariant.
  • All 385 dashboard tests pass, svelte-check clean, dist rebuilt.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved audit-log session expansion to merge fetched results with live-displaced entries arriving mid-load, keeping totals accurate.
    • Prevented duplicate fetching by respecting already loading/loaded thread states.
    • Fixed session/thread counting issues during interleaved live events, avoiding inflated totals.
    • Ensured audit detail handling updates consistently without triggering premature expanded-detail fetches.
    • Retained displaced thread heads when folding into unexpanded threads instead of dropping them.
  • Tests

    • Updated and added coverage for thread head/child merging, preserved-entry behavior, and session counting regressions.

…ssion requests

With "Group by session" on, a live row displaced out of the head list (two
rows of a fresh session collapsing into one thread, or a new live request
replacing the head) was dropped when the thread's children were never
fetched. Every later live event for that request then matched nothing on
screen and was re-counted as a new session member, bumping the thread badge
+1 per event — 2x-3x inflation whenever a session's first messages rendered
live.

Displaced entries are now retained in a partial children list (loaded:
false), so late events merge in place; expanding still triggers the full
fetch, which preserves not-yet-persisted live entries on top of the server
page. Also folds the duplicated audit.detail branch of mergeLiveAuditEntry
into the main flow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 28, 2026 10:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d0577399-ad32-4a78-88ac-f4c5241cb639

📥 Commits

Reviewing files that changed from the base of the PR and between da3e2a6 and d142d30.

⛔ Files ignored due to path filters (2)
  • internal/admin/dashboard/static/dist/assets/index-D0OaGceh.js is excluded by !**/dist/**
  • internal/admin/dashboard/static/dist/index.html is excluded by !**/dist/**
📒 Files selected for processing (2)
  • web/dashboard/src/pages/audit-logs/auditList.svelte.js
  • web/dashboard/tests/audit-list.test.js

📝 Walkthrough

Walkthrough

Audit-log thread handling now preserves live displaced entries during fetches, avoids duplicate expansions, separates detail-event processing, and maintains accurate child and session totals. Tests cover head filtering, live retention, interleaved session requests, and lifecycle updates.

Changes

Audit log thread merging

Layer / File(s) Summary
Thread-child merge contract
web/dashboard/src/pages/audit-logs/audit-logic.js, web/dashboard/tests/audit-list.test.js
Replaces head filtering with a merge helper that preserves unmatched live entries and reports preservedCount.
Live event state and child retention
web/dashboard/src/pages/audit-logs/live-logs-logic.js, web/dashboard/tests/live-logs.test.js
Separates detail-event patches, centralizes other live-state updates, and retains displaced children in unloaded thread placeholders.
Thread expansion and fetch restoration
web/dashboard/src/pages/audit-logs/auditList.svelte.js
Avoids duplicate fetches, restores stale or failed loads, and merges fetched entries with retained live rows while updating totals.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: copilot

Sequence Diagram(s)

sequenceDiagram
  participant LiveEvent
  participant LiveLogMerger
  participant ThreadChildren
  participant AuditList
  participant AuditAPI

  LiveEvent->>LiveLogMerger: merge audit event
  LiveLogMerger->>ThreadChildren: retain or prepend displaced child
  AuditList->>AuditAPI: fetch thread entries
  AuditAPI-->>AuditList: fetched entries and total
  AuditList->>ThreadChildren: merge fetched and preserved entries
  ThreadChildren-->>AuditList: loaded entries and adjusted total
Loading

Poem

A rabbit watched the audit stream,
And kept live rows within the dream.
Threads merged gently, counts stayed right,
Detail hops skipped their extra flight.
“Fetch and fold!” the bunny sang.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly summarizes the main fix: preventing inflated live session-thread counts.
Description check ✅ Passed The description covers the problem, root cause, fix, tests, and impact, even though it doesn't use the exact template headings.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/live-thread-count

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/dashboard/src/pages/audit-logs/auditList.svelte.js`:
- Around line 228-247: Update the restore callback and related toggleThread
state handling so deleting an empty auditThreadChildren[sessionId] also clears
or marks auditExpandedThreads[sessionId] for retry. Ensure the next toggle after
a failed or stale empty fetch invokes fetchThreadEntries immediately rather than
being interpreted as a collapse.
- Around line 262-271: Update the merge call in fetchThreadEntries so the old
in-flight head is not included in the headKeys used by mergeAuditThreadChildren;
pass only the currently valid head and currentHead as appropriate, preserving
the demoted head as a live child. Add a regression test covering a
foldLiveAuditIntoThread/regroupLiveAuditHead head replacement while
fetchThreadEntries is in flight, verifying the displaced child remains in the
merged results.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0d8b9346-e42a-4cc5-8b48-7d3316b651f9

📥 Commits

Reviewing files that changed from the base of the PR and between 01d6f30 and da3e2a6.

⛔ Files ignored due to path filters (2)
  • internal/admin/dashboard/static/dist/assets/index-BmAzcbJM.js is excluded by !**/dist/**
  • internal/admin/dashboard/static/dist/index.html is excluded by !**/dist/**
📒 Files selected for processing (5)
  • web/dashboard/src/pages/audit-logs/audit-logic.js
  • web/dashboard/src/pages/audit-logs/auditList.svelte.js
  • web/dashboard/src/pages/audit-logs/live-logs-logic.js
  • web/dashboard/tests/audit-list.test.js
  • web/dashboard/tests/live-logs.test.js

Comment thread web/dashboard/src/pages/audit-logs/auditList.svelte.js
Comment thread web/dashboard/src/pages/audit-logs/auditList.svelte.js
Comment thread web/dashboard/src/pages/audit-logs/auditList.svelte.js Outdated
@greptile-apps

greptile-apps Bot commented Jul 28, 2026

Copy link
Copy Markdown

Confidence Score: 5/5

The PR appears safe to merge.

The current implementation excludes only the replacement head when an in-flight session fetch completes, allowing the displaced original head to remain in the merged child list; no blocking failure remains.

T-Rex T-Rex Logs

What T-Rex did

  • The initial regression run failed at the final count assertion with 6 !== 2.
  • After fixes, the named regression test passed (1/1) with exit code 0.
  • The post-change run verified the key assertions: head, session_count [REDACTED] 2, total 1, flushed child a, and children.loaded === false.
  • Two log artifacts were collected to document both the pre-change failure and the post-change result.

View all artifacts

T-Rex Ran code and verified through T-Rex

Reviews (2): Last reviewed commit: "fix(dashboard): keep mid-flight demoted ..." | Re-trigger Greptile

…retry collapsed failed fetches

Review follow-ups: exclude only the CURRENT head when merging a fetched
session page (a head replaced during the fetch is a demoted child the page
must keep), and collapse an expanded thread when a failed/stale fetch leaves
nothing to show so a single click retries instead of three.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 28, 2026 10:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@SantiagoDePolonia

Copy link
Copy Markdown
Contributor Author

Addressed both review findings in d142d30:

Head replacement mid-flight (CodeRabbit major / Greptile P1) — confirmed, fixed as proposed. Excluding the original head alongside currentHead was backwards: when a live request replaces the head during the session-page fetch, the original head is a demoted child that the fetched page must keep. mergeAuditThreadChildren now excludes only the current on-screen head (currentHead ? [currentHead] : [head]). When the head object was merely replaced by a live merge of the same request, the identity keys are identical, so behavior there is unchanged. Added a regression test for the race (mergeAuditThreadChildren keeps a head demoted while the fetch was in flight).

Failed/stale fetch leaves a dead expanded thread (CodeRabbit minor) — valid, pre-existing before this PR, fixed. When the restore path drops an empty slot, the thread is now also collapsed, so a single click retries instead of three. When live-displaced entries remain, the thread stays expanded showing them, and the next expand still refetches (loaded: false).

@SantiagoDePolonia
SantiagoDePolonia merged commit dce2614 into main Jul 28, 2026
20 checks passed
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.

3 participants