Skip to content

feat(users-page): filter heartbeats + openclaw metadata in events path + Model column#4

Merged
alexanderkreidich merged 3 commits into
openclaw-stablefrom
claude/openclaw-users-page-columns
Apr 19, 2026
Merged

feat(users-page): filter heartbeats + openclaw metadata in events path + Model column#4
alexanderkreidich merged 3 commits into
openclaw-stablefrom
claude/openclaw-users-page-columns

Conversation

@alexanderkreidich

Copy link
Copy Markdown

Summary

Closes three observability gaps visible on claw-world's OpenClaw deployments (Linear 2BB-294 / 2BB-295 / 2BB-298).

1. Hide LiteLLM heartbeat probes from Users + Traces (2BB-294)

LiteLLM's internal health-check probes create trace rows tagged litellm-internal-health-check. Those were leaking into:

  • Users page counts (`getUsersFromEventsTable`, `getUsersCountFromEventsTable`, `getUserMetricsFromEventsTable`)
  • `getTracesGroupedByUsers` / `getTotalUserCount` / `getUserMetrics` on the traces path

Add `NOT has(..., 'litellm-internal-health-check')` to each path so internal probes never pollute user-facing aggregates.

2. Read openclaw_* metadata from the events path (2BB-295)

The Users page already reads Channel from `trace.metadata['openclaw_channel']` and Username from a coalesce of `openclaw_sender_username` / `_sender_name` / `_sender_label` (landed in #1). But the events-table aggregation path in `getUserMetricsFromEventsTable` didn't. Add the same projection using `mapFromArrays(arrayReverse(...))` so the columns stay populated under the events-backed code path.

3. Surface model as a first-class Traces column (2BB-298)

LiteLLM names traces `litellm-<call_type>/`. Add a Model column to the Traces table, preferring `metadata.model` when present and falling back to the suffix of the trace name. Hidden by default but enableable via the column picker.

Verified with

claw-world's `infra/test/` harness against this branch:

[PASS] admin endpoints hidden
[PASS] metadata channel/username present: channel='mattermost' username='alice'
[PASS] readable input (no truncation)
[PASS] cost > 0 and tokens > 0
[PASS] model in trace name: name='litellm-aresponses/mock/gpt'

Users page shows alice/mattermost, bob/mattermost, carol/slack with Channel + Username columns populated; heartbeat-tagged rows absent from Traces view.

Related

…nts; add Model column

Three observability gaps on downstream OpenClaw deployments (2BB-294/295/298):

1) LiteLLM's internal health-check probes create trace rows tagged
   `litellm-internal-health-check`. Those were leaking into the Users page
   counts and into the Traces view. Exclude them from the events-table
   user aggregations and from `getTracesGroupedByUsers` / `getTotalUserCount`
   / `getUserMetrics`.

2) The Users page already reads Channel + Username from
   `trace.metadata['openclaw_channel']` + coalesced
   `openclaw_sender_username/name/label`, but the events-table aggregation
   path (`getUserMetricsFromEventsTable`) didn't. Add the same projection
   there so the columns stay populated under the events-backed code path.

3) LiteLLM names traces `litellm-<call_type>/<model>`. Surface the model
   as a first-class sortable column in the Traces table, preferring
   `metadata.model` when present and falling back to the suffix of the
   trace name.
@github-actions

Copy link
Copy Markdown

@claude review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b0bbf38de1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

.where(appliedEventsFilter)
.whereRaw("e.user_id IS NOT NULL AND length(e.user_id) > 0")
.whereRaw("e.is_deleted = 0")
.whereRaw("NOT has(e.tags, 'litellm-internal-health-check')")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Align hasAny checks with heartbeat filtering

This exclusion is applied to user list/count/metrics queries, but hasAnyUser/hasAnyUserFromEventsTable still count heartbeat-tagged rows. In projects where heartbeat probes are the only tagged user rows, the users page will report data exists (so onboarding is hidden) while the table endpoints return zero rows, producing an inconsistent empty state. Apply the same heartbeat predicate in the hasAny* queries so the gating logic matches the filtered datasets.

Useful? React with 👍 / 👎.

Addresses Codex review on #4:

getUsersFromEventsTable / getUsersCountFromEventsTable /
getUserMetricsFromEventsTable / getTracesGroupedByUsers /
getTotalUserCount / getUserMetrics all skip rows tagged
`litellm-internal-health-check`, but hasAnyUser and
hasAnyUserFromEventsTable did not. In projects where heartbeat probes are
the only tagged user rows, the onboarding gate saw "data exists" while
the filtered list/metrics endpoints returned empty — an inconsistent
empty state where the wizard stayed hidden on a seemingly-empty page.

Mirror the same `NOT has(tags, 'litellm-internal-health-check')` predicate
on both hasAny* queries so the gating logic lines up with the filtered
datasets.
@alexanderkreidich

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e35a8d83c0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +621 to +625
const metadata = row.original.metadata as
| { model?: unknown }
| null
| undefined;
const metadataModel =

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Source model value from data that is actually loaded

This cell reads row.original.metadata.model, but the traces table rows do not include metadata in the data returned by api.traces.all (the shared TracesTableUiReturnType omits it, and the local row mapping only copies explicit fields), so metadataModel is always undefined. In practice, traces that carry metadata.model but whose name does not match the litellm-.../... pattern will render an empty Model column even though the model is available.

Useful? React with 👍 / 👎.

Addresses Codex P2 on #4: `TracesTableUiReturnType` does not expose
`metadata`, and the row mapping only copies explicit fields, so
`row.original.metadata.model` was always `undefined`. The live Model
value comes from parsing the LiteLLM trace name
(`litellm-<endpoint>/<model>`). Remove the dead branch and keep only
the name-parsing fallback.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@alexanderkreidich alexanderkreidich merged commit 15069e3 into openclaw-stable Apr 19, 2026
2 checks passed
@alexanderkreidich alexanderkreidich deleted the claude/openclaw-users-page-columns branch April 19, 2026 21:16
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