Skip to content

feat: add user-agent support in gRPC client channel options#6808

Merged
bijin-bruno merged 4 commits intousebruno:mainfrom
sanish-bruno:fix/grpc-user-agent
Jan 20, 2026
Merged

feat: add user-agent support in gRPC client channel options#6808
bijin-bruno merged 4 commits intousebruno:mainfrom
sanish-bruno:fix/grpc-user-agent

Conversation

@sanish-bruno
Copy link
Collaborator

@sanish-bruno sanish-bruno commented Jan 14, 2026

Jira

Fix: Allow custom User-Agent for gRPC requests

Fixes #6800

Description

This PR enables users to set a custom User-Agent header for gRPC requests via the metadata configuration in Bruno.

Problem

When users set a User-Agent in their gRPC metadata headers, it was being ignored. The server would always receive the default grpc-node-js/X.X.X user-agent from the @grpc/grpc-js library.

Solution

The custom User-Agent header is now extracted from the request headers and passed as the grpc.primary_user_agent channel option to @grpc/grpc-js. This prepends the custom user-agent to the default one.

Example:

  • User sets: User-Agent: my-custom-agent
  • Server receives: my-custom-agent grpc-node-js/1.13.3

Why can't we completely replace the user-agent?

The @grpc/grpc-js library does not support completely replacing the user-agent. The library always appends its own identifier (grpc-node-js/VERSION) to the user-agent string. This is by design and is a known limitation:

The only options provided by @grpc/grpc-js are:

  • grpc.primary_user_agent - prepends to the default user-agent
  • grpc.secondary_user_agent - appends to the default user-agent

Changes

  • startConnection: Extracts User-Agent from request headers and adds it as grpc.primary_user_agent channel option
  • loadMethodsFromReflection: Same handling for reflection calls

Contribution Checklist:

  • I've used AI significantly to create this pull request
  • The pull request only addresses one issue or adds one feature.
  • The pull request does not introduce any breaking changes
  • I have added screenshots or gifs to help explain the change if applicable.
  • I have read the contribution guidelines.
  • Create an issue and link to the pull request.

Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.

Publishing to New Package Managers

Please see here for more information.

Summary by CodeRabbit

  • Bug Fixes

    • Consistently capture user-agent headers (case-insensitive) and apply them to gRPC connections and service reflection, improving compatibility and observability.
    • Public connection APIs now accept channel options so client and reflection calls receive merged runtime options.
  • Tests

    • Added comprehensive tests validating header extraction, option merging behavior, and scenarios when a user-agent is absent.

✏️ Tip: You can customize this high-level summary in your review settings.

- Extracted user-agent from request headers and set it as grpc.primary_user_agent channel option.
- Updated client instantiation to merge user-agent with existing channel options for enhanced request handling.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 14, 2026

Walkthrough

GrpcClient now extracts the User-Agent header case-insensitively, merges it into gRPC channel options as grpc.primary_user_agent (defaulting channelOptions = {}), and passes the merged options to both the reflection client and runtime gRPC client; tests validating these behaviors were added.

Changes

Cohort / File(s) Summary
gRPC client behavior
packages/bruno-requests/src/grpc/grpc-client.js
Adds case-insensitive extraction of User-Agent from request headers, merges into provided channelOptions as grpc.primary_user_agent (ensuring channelOptions = {} default), and uses mergedChannelOptions for reflection client creation and runtime client instantiation. Signatures updated to accept channelOptions with default.
Tests
packages/bruno-requests/src/grpc/grpc-client.spec.js
New Jest test suite mocking grpc-js and reflection client to verify User-Agent extraction across header casings, correct merging and ordering of grpc.primary_user_agent into channel options, and behavior for both reflection and startConnection flows.

Sequence Diagram(s)

sequenceDiagram
    participant Request as Request (headers)
    participant GrpcClient as GrpcClient
    participant ReflectionClient as ReflectionClient
    participant Server as gRPC Server

    Request->>GrpcClient: provide headers + channelOptions
    GrpcClient->>GrpcClient: extract User-Agent (case-insensitive)
    GrpcClient->>GrpcClient: build mergedChannelOptions (include grpc.primary_user_agent)
    GrpcClient->>ReflectionClient: create reflection client with mergedChannelOptions
    ReflectionClient->>GrpcClient: return service & method descriptors
    GrpcClient->>Server: instantiate runtime gRPC Client with mergedChannelOptions
    GrpcClient->>Server: make RPC calls using mergedChannelOptions
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

size/M

Suggested reviewers

  • helloanoop
  • lohit-bruno
  • naman-bruno

"A header once lost, now clearly named,
Case-folded, merged, its value reclaimed,
Reflection peeks, the client speaks true,
Tests nod in chorus—behavior renewed,
Bruno hands off the agent it was claimed."

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding user-agent support to gRPC client channel options, which directly addresses the PR's primary objective.
Linked Issues check ✅ Passed Changes implement the core requirement from #6800: extracting User-Agent headers and applying them as grpc.primary_user_agent in channel options for both startConnection and loadMethodsFromReflection.
Out of Scope Changes check ✅ Passed All changes are directly scoped to user-agent handling in gRPC client channel options; no unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
packages/bruno-requests/src/grpc/grpc-client.js (2)

531-540: Add defensive check for request.headers before accessing keys.

If request.headers is undefined or null, Object.keys(request.headers) will throw. Consider adding a guard:

-    const userAgentKey = Object.keys(request.headers).find(
+    const userAgentKey = Object.keys(request.headers || {}).find(
      (key) => key.toLowerCase() === 'user-agent'
    );

631-637: Same defensive check needed; consider extracting shared logic.

This user-agent extraction duplicates lines 531-540. Apply the same request.headers || {} guard here. Optionally, extract a helper like #extractUserAgentChannelOption(headers, channelOptions) to reduce duplication.

-    const userAgentKey = Object.keys(request.headers).find(
+    const userAgentKey = Object.keys(request.headers || {}).find(
      (key) => key.toLowerCase() === 'user-agent'
    );

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a6e3dd9 and c94521a.

📒 Files selected for processing (2)
  • packages/bruno-requests/src/grpc/grpc-client.js
  • packages/bruno-requests/src/grpc/grpc-client.spec.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/bruno-requests/src/grpc/grpc-client.spec.js
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation. No tabs, just spaces
Stick to single quotes for strings. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
Space before and after the arrow in arrow functions. () => {} is good
No space between function name and parentheses. func() not func ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-requests/src/grpc/grpc-client.js
🧠 Learnings (2)
📚 Learning: 2025-12-02T07:24:50.311Z
Learnt from: bijin-bruno
Repo: usebruno/bruno PR: 6263
File: packages/bruno-requests/src/auth/oauth2-helper.ts:249-249
Timestamp: 2025-12-02T07:24:50.311Z
Learning: In OAuth2 Basic Auth headers for Bruno, clientSecret is optional and can be omitted. When constructing the Authorization header in `packages/bruno-requests/src/auth/oauth2-helper.ts`, use `clientSecret || ''` instead of `clientSecret!` to properly handle cases where only clientId is provided, per community requests.

Applied to files:

  • packages/bruno-requests/src/grpc/grpc-client.js
📚 Learning: 2025-12-17T21:41:24.730Z
Learnt from: naman-bruno
Repo: usebruno/bruno PR: 6407
File: packages/bruno-app/src/components/Environments/ConfirmCloseEnvironment/index.js:5-41
Timestamp: 2025-12-17T21:41:24.730Z
Learning: Do not suggest PropTypes validation for React components in the Bruno codebase. The project does not use PropTypes, so reviews should avoid proposing PropTypes and rely on the existing typing/validation approach (e.g., TypeScript or alternative runtime checks) if applicable. This guideline applies broadly to all JavaScript/JSX components in the repo.

Applied to files:

  • packages/bruno-requests/src/grpc/grpc-client.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: CLI Tests
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: Unit Tests
🔇 Additional comments (3)
packages/bruno-requests/src/grpc/grpc-client.js (3)

538-540: Verify intended override precedence.

The spread order means channelOptions['grpc.primary_user_agent'] (if provided by caller) will override the extracted header value. If the header should always win, reverse the spread:

const mergedChannelOptions = userAgentValue
  ? { ...channelOptions, 'grpc.primary_user_agent': userAgentValue }
  : channelOptions;

If caller override is intentional, current code is fine.


541-543: LGTM on client instantiation with merged options.

The mergedChannelOptions is correctly passed to the generic client constructor, enabling grpc.primary_user_agent to be applied to the channel.


654-654: LGTM on reflection client options.

The merged channel options are correctly propagated to #getReflectionClient, ensuring user-agent is applied during reflection calls as well.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/bruno-requests/src/grpc/grpc-client.js (1)

563-565: Remove the user-agent header from the metadata object to prevent duplication.

The code extracts the user-agent from request.headers and correctly sets it as grpc.primary_user_agent in the channel options (lines 530–539), which is how @grpc/grpc-js manages the User-Agent header. However, the same header is then added again to the metadata object (lines 563–565) via the Object.entries loop, causing duplication. Filter it out when populating metadata:

Suggested fix
Object.entries(request.headers).forEach(([name, value]) => {
  if (name.toLowerCase() !== 'user-agent') {
    metadata.add(name, value);
  }
});
🧹 Nitpick comments (2)
packages/bruno-requests/src/grpc/grpc-client.js (2)

531-543: User-agent extraction logic is correct but duplicated.

The case-insensitive extraction and channel option assignment work as intended. However, this same logic appears again in loadMethodsFromReflection (lines 631-637). Consider extracting to a helper function to avoid duplication.

Also, note that the spread order { 'grpc.primary_user_agent': userAgentValue, ...channelOptions } means an explicit grpc.primary_user_agent in channelOptions will override the header-derived value. If you intend for the header to always take precedence, reverse the spread order.

♻️ Suggested helper extraction
+/**
+ * Extracts user-agent from headers (case-insensitive) and merges into channel options
+ * `@param` {Object} headers - Request headers
+ * `@param` {Object} channelOptions - Existing channel options
+ * `@returns` {Object} Merged channel options with grpc.primary_user_agent if present
+ */
+const mergeUserAgentIntoChannelOptions = (headers, channelOptions = {}) => {
+  const userAgentKey = Object.keys(headers || {}).find(
+    (key) => key.toLowerCase() === 'user-agent'
+  );
+  const userAgentValue = userAgentKey ? headers[userAgentKey] : null;
+  return userAgentValue
+    ? { 'grpc.primary_user_agent': userAgentValue, ...channelOptions }
+    : channelOptions;
+};

Then use it in both locations:

const mergedChannelOptions = mergeUserAgentIntoChannelOptions(request.headers, channelOptions);

626-637: Inconsistent default for channelOptions parameter.

In startConnection, channelOptions defaults to {} (line 478), but here in loadMethodsFromReflection there's no default value. If channelOptions is undefined, the spread will work but the inconsistency is confusing.

♻️ Add default value for consistency
     verifyOptions,
     sendEvent,
-    channelOptions
+    channelOptions = {}
   }) {
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc0bb64 and 7c3dcf5.

📒 Files selected for processing (1)
  • packages/bruno-requests/src/grpc/grpc-client.js
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation. No tabs, just spaces
Stick to single quotes for strings. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
Space before and after the arrow in arrow functions. () => {} is good
No space between function name and parentheses. func() not func ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-requests/src/grpc/grpc-client.js
🧠 Learnings (2)
📚 Learning: 2025-12-02T07:24:50.311Z
Learnt from: bijin-bruno
Repo: usebruno/bruno PR: 6263
File: packages/bruno-requests/src/auth/oauth2-helper.ts:249-249
Timestamp: 2025-12-02T07:24:50.311Z
Learning: In OAuth2 Basic Auth headers for Bruno, clientSecret is optional and can be omitted. When constructing the Authorization header in `packages/bruno-requests/src/auth/oauth2-helper.ts`, use `clientSecret || ''` instead of `clientSecret!` to properly handle cases where only clientId is provided, per community requests.

Applied to files:

  • packages/bruno-requests/src/grpc/grpc-client.js
📚 Learning: 2025-12-17T21:41:24.730Z
Learnt from: naman-bruno
Repo: usebruno/bruno PR: 6407
File: packages/bruno-app/src/components/Environments/ConfirmCloseEnvironment/index.js:5-41
Timestamp: 2025-12-17T21:41:24.730Z
Learning: Do not suggest PropTypes validation for React components in the Bruno codebase. The project does not use PropTypes, so reviews should avoid proposing PropTypes and rely on the existing typing/validation approach (e.g., TypeScript or alternative runtime checks) if applicable. This guideline applies broadly to all JavaScript/JSX components in the repo.

Applied to files:

  • packages/bruno-requests/src/grpc/grpc-client.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: CLI Tests
  • GitHub Check: Unit Tests
🔇 Additional comments (1)
packages/bruno-requests/src/grpc/grpc-client.js (1)

654-654: LGTM!

The mergedChannelOptions is correctly passed to #getReflectionClient, ensuring the user-agent is applied during reflection calls as well.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@helloanoop
Copy link
Contributor

@sanish-bruno Please see if a test (unit / playwright) can be added to assert this behaviour

@pull-request-size pull-request-size bot added size/L and removed size/S labels Jan 14, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/bruno-requests/src/grpc/grpc-client.spec.js`:
- Around line 38-49: The on method inside createMockRpc is defined as an arrow
function and uses `return this`, which binds to the outer scope instead of the
mock RPC object; change the implementation of createMockRpc so that on is a
normal function (or make it return the mock object explicitly) and return the
mock RPC object from createMockRpc; specifically update createMockRpc and its on
handler so calling mockRpc.on(...) returns the mockRpc instance (refer to
createMockRpc and the on method).
🧹 Nitpick comments (1)
packages/bruno-requests/src/grpc/grpc-client.spec.js (1)

188-204: Key ordering assertion is implementation-sensitive.

This test relies on Object.keys() returning keys in insertion order. While ES2015+ guarantees this for string keys, asserting on key position couples the test to how the production code constructs the object. If someone refactors the merging logic (e.g., using Object.assign() differently), this test could break even if the functional behavior is correct.

Consider whether this ordering is a requirement worth testing, or if testing presence/values is sufficient.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7c3dcf5 and 5000ea4.

📒 Files selected for processing (1)
  • packages/bruno-requests/src/grpc/grpc-client.spec.js
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation. No tabs, just spaces
Stick to single quotes for strings. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
Space before and after the arrow in arrow functions. () => {} is good
No space between function name and parentheses. func() not func ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-requests/src/grpc/grpc-client.spec.js
🧠 Learnings (2)
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.test.{js,jsx,ts,tsx} : Add tests for any new functionality or meaningful changes. If code is added, removed, or significantly modified, corresponding tests should be updated or created

Applied to files:

  • packages/bruno-requests/src/grpc/grpc-client.spec.js
📚 Learning: 2025-12-17T21:41:24.730Z
Learnt from: naman-bruno
Repo: usebruno/bruno PR: 6407
File: packages/bruno-app/src/components/Environments/ConfirmCloseEnvironment/index.js:5-41
Timestamp: 2025-12-17T21:41:24.730Z
Learning: Do not suggest PropTypes validation for React components in the Bruno codebase. The project does not use PropTypes, so reviews should avoid proposing PropTypes and rely on the existing typing/validation approach (e.g., TypeScript or alternative runtime checks) if applicable. This guideline applies broadly to all JavaScript/JSX components in the repo.

Applied to files:

  • packages/bruno-requests/src/grpc/grpc-client.spec.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: CLI Tests
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: Unit Tests
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: SSL Tests - Linux
🔇 Additional comments (2)
packages/bruno-requests/src/grpc/grpc-client.spec.js (2)

276-285: Good test setup for method pre-registration.

Pre-registering the method in grpcClient.methods correctly simulates the state after loadMethodsFromReflection has been called. The serializers are minimal but sufficient for the channel options tests.


86-95: Well-structured test suite with good isolation.

Test setup properly clears mocks and resets state between tests. The coverage addresses the PR objective of verifying User-Agent extraction and channel options merging for both reflection and connection flows. Based on learnings, this satisfies the requirement to add tests for new functionality.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@bijin-bruno bijin-bruno merged commit 725dfea into usebruno:main Jan 20, 2026
8 checks passed
FraCata00 pushed a commit to FraCata00/bruno that referenced this pull request Feb 9, 2026
…#6808)

* feat: add user-agent support in gRPC client channel options

- Extracted user-agent from request headers and set it as grpc.primary_user_agent channel option.
- Updated client instantiation to merge user-agent with existing channel options for enhanced request handling.

* test: add unit tests for GrpcClient user-agent handling

* test: enhance GrpcClient user-agent tests with edge case handling

* test: enhance GrpcClient channelOptions handling with override capability
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gRPC doesn't overwrite user-agent

3 participants