Skip to content

Switch widget UA detection to whitelist#244

Merged
RafaelPo merged 3 commits intomainfrom
doc/user-agent-strategy
Feb 27, 2026
Merged

Switch widget UA detection to whitelist#244
RafaelPo merged 3 commits intomainfrom
doc/user-agent-strategy

Conversation

@RafaelPo
Copy link
Copy Markdown
Contributor

Summary

  • Switch _widgets_from_user_agent() from a blocklist to a whitelist approach
  • Only Claude-User (sent by both Claude.ai and Claude Desktop) gets widgets; all other UAs default to text-only
  • Broaden is_internal_client() to match "everyrow" instead of "everyrow-cc"
  • Document all observed User-Agent strings in code comments

Context

We confirmed the exact UA strings by adding temporary logging to everyrow_balance and testing from Claude.ai, Claude Desktop, and Claude Code:

Client User-Agent Widgets
Claude.ai Claude-User Yes
Claude Desktop Claude-User Yes
Claude Code claude-code/2.1.62 (cli) No
everyrow everyrow-cc/1.0 No

Since we now know the widget-capable UAs, a whitelist is safer than a blocklist — unknown clients won't accidentally get widget JSON they can't render.

Test plan

  • uv run pytest tests/ — 372 passed, 28 skipped
  • Verify Claude.ai still gets widgets via tunnel
  • Verify Claude Code does not get widgets

🤖 Generated with Claude Code

Now that we've confirmed Claude.ai and Claude Desktop both send
"Claude-User", whitelist that UA instead of blocking known non-widget
clients. Unknown UAs default to text-only. Also broaden internal client
detection to match "everyrow" instead of "everyrow-cc".

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@RafaelPo RafaelPo requested a review from jackwildman February 27, 2026 10:58
@RafaelPo
Copy link
Copy Markdown
Contributor Author

@claude code review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 27, 2026

Claude finished @RafaelPo's task in 1m 7s —— View job


Code Review: Switch widget UA detection to whitelist

  • Gather context and read changed files
  • Review the diff for correctness, security, and patterns
  • Post review feedback

Overall: Clean, well-documented change. The whitelist approach is the right call — fail-closed is safer than fail-open for widget detection. One real issue to fix; the rest are minor.


Stale docstring on client_supports_widgets (should fix)

The tier 3 description in the client_supports_widgets docstring (tool_helpers.py:117-126) still describes the old blocklist behavior:

    3. **User-Agent fallback** (stateless HTTP mode):
       ...  Clients known to
       NOT support widgets (e.g. Claude Code) are excluded.  If the
       User-Agent is unknown, we default to **showing widgets** because
       HTTP mode traffic is overwhelmingly from Claude.ai/Desktop.

    Unknown clients default to **no widget** in stateful mode (tier 2),
    but to **widget** in stateless HTTP mode (tier 3) where the population
    is predominantly Claude.ai/Desktop.

With the whitelist change, unknown UAs now default to text-only, which is the exact opposite of what this docstring says. This will mislead future readers.

Fix this →


Minor observations

is_internal_client broadening (tool_helpers.py:193): Matching "everyrow" instead of "everyrow-cc" is intentionally broader per the PR description. Just confirming: any future internal client UA starting with everyrow-* will be caught, which seems like the right intent. No action needed.

No unit tests for _widgets_from_user_agent / is_internal_client: Neither function has test coverage. These are small and deterministic — a parameterized test would lock in the whitelist/internal-client behavior and catch regressions if UAs change. Not blocking, but worth adding.


@RafaelPo
Copy link
Copy Markdown
Contributor Author

@claude code review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 27, 2026

Claude finished @RafaelPo's task in 55s —— View job


Code Review: Switch widget UA detection to whitelist

  • Gather context and read changed files
  • Review the diff against origin/main
  • Post review feedback

Overall: The core change (blocklist → whitelist in _widgets_from_user_agent) is a solid improvement — unknown clients defaulting to text-only is the right call. Two issues to address:

1. Stale docstring in client_supports_widgets (bug)

The docstring at lines 117–126 still describes the old blocklist behavior:

    3. **User-Agent fallback** (stateless HTTP mode):
       When ``client_params`` is ``None`` (stateless HTTPno MCP initialize
       handshake), we check the HTTP User-Agent header.  Clients known to
       NOT support widgets (e.g. Claude Code) are excluded.  If the
       User-Agent is unknown, we default to **showing widgets** because
       HTTP mode traffic is overwhelmingly from Claude.ai/Desktop.

    Unknown clients default to **no widget** in stateful mode (tier 2),
    but to **widget** in stateless HTTP mode (tier 3) where the population
    is predominantly Claude.ai/Desktop.

This is now wrong — tier 3 also defaults to no widgets for unknown UAs. This should be updated to match the new whitelist logic, otherwise the next person reading this will be misled about the fallback behavior.

Fix this →

2. is_internal_client broadening — intentional?

Changing "everyrow-cc""everyrow" is a wider match. The PR body and the UA comment (line 178) say the internal client now sends "everyrow/1.0", so this is consistent. Just confirming: this means any future UA containing the substring "everyrow" (e.g. "everyrow-analytics/1.0") would also be treated as internal. That's likely fine given you control the namespace, but worth being aware of.

Looks good

  • Whitelist approach with _WIDGET_UA_SUBSTRINGS = {"claude-user"} and .lower() comparison is clean
  • The inline UA documentation is thorough and useful for future maintainers
  • Logic is simple and easy to follow

Update client_supports_widgets docstring to reflect whitelist behavior
(unknown UAs now default to no widget, not widget). Add parameterized
tests for _widgets_from_user_agent and is_internal_client.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@RafaelPo
Copy link
Copy Markdown
Contributor Author

@claude code review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Feb 27, 2026

Claude finished @RafaelPo's task in 45s —— View job


Code Review: Switch widget UA detection to whitelist

  • Gather context and read changed files
  • Review the diff for correctness, security, and patterns
  • Post review feedback

Overall: Clean PR, ready to merge. The previous review concerns (stale docstring, missing tests) have both been addressed in follow-up commits. No new issues found.


Whitelist logic (tool_helpers.py:158-185): Correct and straightforward. The _WIDGET_UA_SUBSTRINGS = {"claude-user"} with .lower() comparison is a solid fail-closed default — unknown clients get text-only, which is the safe choice.

Docstring (tool_helpers.py:117-125): Now accurately reflects the whitelist behavior. Fixed in e4a33de.

is_internal_client broadening (tool_helpers.py:192): Matching "everyrow" instead of "everyrow-cc" is consistent with the observed UA "everyrow/1.0" and forward-compatible with any future everyrow-* clients. No issues.

Tests (test_ua_detection.py): Good parameterized coverage for both _widgets_from_user_agent and is_internal_client, including edge cases (empty string, substring matches, case variations). Addresses the gap called out in earlier reviews.

LGTM.


@RafaelPo RafaelPo merged commit 5cd8738 into main Feb 27, 2026
5 checks passed
@RafaelPo RafaelPo deleted the doc/user-agent-strategy branch February 27, 2026 11:03
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