Skip to content

Implement gRPC X-User-Agent Header for SDK IdentificationΒ #2169

Description

@rwalworth

πŸ§‘β€πŸŽ“ Beginner Issue

Welcome! This is a Beginner Issue designed to help you learn the codebase.

🏁 When this issue is complete, you will have:

βœ… Researched gRPC interceptors and metadata in Python
βœ… Followed SDK patterns to implement a solution
βœ… Researched and identified appropriate programming constructs to solve the problem
βœ… Created basic tests
βœ… Delivered a clean, review-ready pull request


🐞 Problem Description

Every gRPC call the Python SDK makes to a Hiero consensus node or mirror node is currently sent without any identifying information about the SDK. The consensus node has a built-in usage tracker (GrpcUsageTracker) that reads a X-User-Agent gRPC metadata header from incoming requests and uses it to log SDK type and version metrics. Without this header, the Python SDK is logged as UNKNOWN in the consensus node's telemetry.

The consensus node reads the header using this exact key:

Key.of("X-User-Agent", Metadata.ASCII_STRING_MARSHALLER)  // GrpcUsageTracker.java

And parses values in the format {sdk-name}/{version}, for example:

hiero-sdk-python/2.1.5

The supported SDK name for Python is hiero-sdk-python (see UserAgentType.java).

Current behavior: gRPC calls are made with no X-User-Agent header, so all Python SDK traffic appears as UNKNOWN in consensus node metrics.

Desired behavior: Every outgoing gRPC call from the Python SDK includes a X-User-Agent: hiero-sdk-python/{version} metadata header, enabling the consensus node to correctly attribute traffic.

Files to look at:

  • src/hiero_sdk_python/node.py β€” where consensus node gRPC channels are created (grpc.secure_channel, grpc.insecure_channel)
  • src/hiero_sdk_python/client/client.py β€” where mirror node gRPC channels are created
  • src/hiero_sdk_python/channels.py β€” the _Channel wrapper around gRPC channels
  • src/hiero_sdk_python/executable.py β€” where stub methods are actually called (_execute_method)

πŸ’‘ Expected Solution

Implement a gRPC client interceptor that injects the X-User-Agent: hiero-sdk-python/{version} metadata header on every outgoing gRPC call. Apply this interceptor to all channels created in the SDK (both consensus node and mirror node channels).

The header value must exactly match the format expected by the consensus node:

hiero-sdk-python/2.1.5

where the version comes from the installed package version at runtime.


πŸ” Research Pointers

gRPC Python interceptors (start here):

How other Hiero SDKs implement this:

  • hiero-sdk-js β€” uses gRPC metadata injected per-call
  • hiero-sdk-go β€” uses a UnaryClientInterceptor that appends user-agent metadata; look for the interceptor implementation

How the consensus node processes this header:

  • GrpcUsageTracker.java β€” the server interceptor that reads the header
  • UserAgent.java β€” how the header value is parsed (format: sdk-name/semver, e.g. hiero-sdk-python/2.1.5; "dev" is also accepted as a version)
  • UserAgentType.java β€” confirms hiero-sdk-python is a recognised SDK name

Getting the package version at runtime in Python:

  • importlib.metadata β€” standard library module; use importlib.metadata.version("hiero-sdk-python") to get the installed version at runtime

πŸ› οΈ Implementation Notes

  1. Research how Python gRPC interceptors work before writing any code β€” specifically how grpc.intercept_channel() wraps a channel and how ClientCallDetails is used to pass metadata. Look at the Go SDK interceptor as an analogous example.

  2. Create an interceptor class that implements the required interceptor interfaces and injects the X-User-Agent header into the gRPC call metadata. Consider which call types this SDK uses (unary? server-streaming?) and implement the appropriate interfaces for each.

  3. Look up the package version at runtime using importlib.metadata. Handle the case where the version is unavailable gracefully (e.g., in a development checkout where the package is not installed β€” fall back to "dev").

  4. Apply the interceptor when channels are created. Find where grpc.secure_channel() and grpc.insecure_channel() are called in node.py and client/client.py, and wrap the resulting channel with grpc.intercept_channel().

  5. Keep it minimal β€” the interceptor's only job is to append this one header. It should not modify anything else about the call.


🧠 Beginner Contributors β€” Prerequisites & Expectations

Caution

Beginner issues are low-risk but we expect a working solution.

Important

We recommend completing at least 3 good first issues before attempting a beginner issue.

Difficulty:

  • Requires building knowledge of specific files in the gRPC channel setup
  • Beginner to intermediate coding skills; familiarity with Python class inheritance

Tip

You should be comfortable with:

  • Forking, branching, committing (with DCO + GPG signing), and opening a pull request without a tutorial
  • Reading beginner to intermediate code you did not write and following its patterns
  • Handling simple merge conflicts
  • Keeping your fork up to date with main by rebasing
  • Looking things up on your own before asking for help

If any of that feels unfamiliar, good first issues might still be the most rewarding path for you right now.

⚠️ AI Usage Policy

  • AI can help you understand the codebase and the problem
  • Using AI to generate code for beginner issues is strictly discouraged
  • Using AI as the main source of research is strictly discouraged β€” refer to language and library documentation, protobuf definitions, and other SDKs

⏱️ Timeline & Workflow

  • Typical time: ~1 week / ~8 hours
    🧩 Difficulty: Manageable but requires investigation and testing
    πŸŽ“ Best for: Beginner contributors

  • πŸ”΄ Completing this issue in 1–3 hours is a red flag

Tip

Suggested: share your proposed implementation approach as a comment before writing code to get early feedback and avoid wasted effort.


πŸ§ͺ Testing Requirements

Important

At the beginner level, testing will be necessary but a small component.
The methods you implement should be verified (happy path at least).

Source code changes (in src/):

  • Write unit tests that verify the interceptor injects the correct X-User-Agent header value
  • Test that the version string is included correctly and falls back gracefully when unavailable
  • Run tests locally: uv run pytest tests/unit/ -v
  • Integration tests will run automatically when you push

πŸ›‘οΈ Quality & Review Standards

Beginner PRs must be working and follow best practices.

  1. Working: The implementation must inject the correct header on all outgoing gRPC calls.
  2. SDK Best Practices: The interceptor should follow the patterns used in node.py and channels.py β€” do not introduce a new style or abstraction.
  3. Testing: Unit tests that verify the header is injected with the correct key and value format.

⚠️ Breaking changes

  • This change should be transparent to SDK users β€” it adds metadata to gRPC calls but does not change any public API.

βœ… PR Quality Checklist

Before opening your PR, confirm:

  • I have spent the majority of my time researching the problem and building my understanding of the codebase and methods.
  • My implementation works and is tested.
  • Every line of code is personally understood and explainable.
  • My changes address what the issue asked for β€” nothing more, nothing less.
  • I have applied appropriate linting, code quality, and formatting tools used in this repo.

Before requesting a review, confirm:

  • I did not modify files unrelated to this issue
  • Clean git history β€” no rebase artifacts, merge commits, or unrelated files
  • My commits are signed: git commit -S -s -m "chore: description" β€” Signing guide
  • I added a CHANGELOG.md entry under [Unreleased] β€” Changelog guide
  • I have included appropriate tests and all CI checks pass.

πŸ“‹ Workflow Quick Reference

Step Guide
Claim this issue Comment /assign below
Sync with main Rebasing guide
Open a PR and link this issue PR guide
Resolve merge conflicts Merge conflicts guide

πŸ“š Resources & Support

πŸ†˜ Stuck?

Tip

Comment on this issue and describe what you have tried. A maintainer will respond.

Python SDK References:

References:

Metadata

Metadata

Assignees

Labels

skill: beginnerAchievable by a fairly new comer that has already completed a couple of good first issues

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions