feat(client): add optional name for endpoints#71
Conversation
|
Documentation for this PR has been generated and is available at: https://n0-computer.github.io/iroh-services/pr/71/docs/iroh_services/ Last updated: 2026-04-11T10:39:05Z |
There was a problem hiding this comment.
Pull request overview
Adds an optional, human-readable endpoint label that can be sent during authentication so endpoints are easier to identify in metrics/observability.
Changes:
- Extend
Authprotocol message with an optionallabel: Option<String>. - Add a
ClientBuilder::label(...)API and plumb the value into the client actor’sAuthRPC. - Update examples and add tests around label behavior / compatibility.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
src/protocol.rs |
Adds Auth.label field and introduces serialization/compatibility tests. |
src/client.rs |
Adds builder support for setting a label and sends it in the Auth RPC; adds a unit test. |
examples/quickstart.rs |
Demonstrates setting a client label. |
examples/net_diagnostics.rs |
Demonstrates setting a client label for net diagnostics usage. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
505136d to
165238c
Compare
|
this is a breaking change, as the API is breaking, it is also breaking on the wire, as sending the new protocol to an old client will break, if the name is set |
|
@dignifiedquire yes, I had bumped semver to be a breaking change, looks like you moved it back down? 165238c#diff-2e9d962a08321605940b5a657135052fbcef87b5e360662bb527c96d9a615542R3 |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
fml that was a pain, but I've implemented this as a non-breaking set of changes. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 6 changed files in this pull request and generated 10 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "permissions": { | ||
| "allow": [ | ||
| "mcp__acp__Edit", | ||
| "mcp__acp__Bash" | ||
| ] | ||
| } |
There was a problem hiding this comment.
This looks like a machine-/developer-local Claude configuration file (and it grants tool permissions). It’s usually not appropriate to commit settings.local.json to the repository; consider removing it from the PR and adding .claude/settings.local.json to .gitignore instead.
| "permissions": { | |
| "allow": [ | |
| "mcp__acp__Edit", | |
| "mcp__acp__Bash" | |
| ] | |
| } |
| #[error("Label is too long (must be no more than {LABEL_MAX_LENGTH} characters).")] | ||
| TooLong, | ||
| #[error("Label is too short (must be at least {LABEL_MIN_LENGTH} characters).")] |
There was a problem hiding this comment.
ValidateLabelError messages talk about "characters", but validation uses label.len() which is UTF-8 byte length (and the docs above mention bytes). Please either change the validation to count characters (or grapheme clusters), or update the error text to say "bytes" to match the actual constraint.
| #[error("Label is too long (must be no more than {LABEL_MAX_LENGTH} characters).")] | |
| TooLong, | |
| #[error("Label is too short (must be at least {LABEL_MIN_LENGTH} characters).")] | |
| #[error("Label is too long (must be no more than {LABEL_MAX_LENGTH} bytes).")] | |
| TooLong, | |
| #[error("Label is too short (must be at least {LABEL_MIN_LENGTH} bytes).")] |
|
|
||
| let too_long_name = "👋".repeat(129); | ||
| let Err(err) = Client::builder(&endpoint).label(&too_long_name) else { | ||
| panic!("label should fail for strings over 1024 bytes"); |
There was a problem hiding this comment.
This panic message says "over 1024 bytes", but the max is LABEL_MAX_LENGTH = 128 (bytes). Please update the message so failures are self-explanatory and consistent with the actual constraint.
| panic!("label should fail for strings over 1024 bytes"); | |
| panic!("label should fail for strings over 128 bytes"); |
| [package] | ||
| name = "iroh-services" | ||
| version = "0.12.0" | ||
| version = "0.11.0" |
There was a problem hiding this comment.
The crate version is being decreased from 0.12.0 to 0.11.0. Cargo package versions should be monotonically increasing; this will confuse downstream users and can break publishing. Please revert to 0.12.0 (or bump forward appropriately) and regenerate the lockfile accordingly.
| version = "0.11.0" | |
| version = "0.12.0" |
| /// from this endpoint easier to identify. This is often used for associating | ||
| /// with other services in your app, like a database user id. | ||
| /// | ||
| /// When this builder method is called, the provided label label is sent |
There was a problem hiding this comment.
Doc typo: "the provided label label" has a duplicated word. Please fix to avoid confusion in the public API docs.
| /// When this builder method is called, the provided label label is sent | |
| /// When this builder method is called, the provided label is sent |
| self.message_channel | ||
| .send(ClientActorMessage::ReadLabel { done: tx }) | ||
| .await | ||
| .map_err(|_| Error::Other(anyhow!("sending ping request")))?; |
There was a problem hiding this comment.
The error context here says "sending ping request", but this code is sending a ReadLabel request. This makes debugging harder; please update the message to reflect the actual operation (e.g. "sending label read request").
| .map_err(|_| Error::Other(anyhow!("sending ping request")))?; | |
| .map_err(|_| Error::Other(anyhow!("sending label read request")))?; |
| /// | ||
| /// labels can be any UTF-8 string, with a min length of 2 bytes, and | ||
| /// maximum length of 128 bytes. **label uniqueness is not enforced.** | ||
| pub async fn set_label(&self, label: String) -> Result<(), Error> { |
There was a problem hiding this comment.
Client::set_label (and set_label_inner) does not validate the label length, even though the docs claim min/max constraints and the builder enforces them. This makes the API inconsistent and allows invalid labels to be sent at runtime; consider reusing the same validation logic in set_label and returning a structured validation error.
| pub async fn set_label(&self, label: String) -> Result<(), Error> { | |
| pub async fn set_label(&self, label: String) -> Result<(), Error> { | |
| let len = label.as_bytes().len(); | |
| if len < 2 || len > 128 { | |
| return Err(Error::Other(anyhow!( | |
| "label length must be between 2 and 128 bytes (got {} bytes)", | |
| len | |
| ))); | |
| } |
| } | ||
| } | ||
| ClientActorMessage::LabelEndpoint{ label, done } => { | ||
| let res = self.send_label_endpoint(label).await; |
There was a problem hiding this comment.
ClientActor keeps a local label, but it is never updated when a LabelEndpoint request succeeds. As a result, Client::label() will keep returning the initial builder value (or None) even after calling Client::set_label. Consider updating self.label = Some(label) after a successful RPC, so the local read reflects the latest label.
| let res = self.send_label_endpoint(label).await; | |
| let res = self.send_label_endpoint(label.clone()).await; | |
| if res.is_ok() { | |
| self.label = Some(label); | |
| } |
| message_channel: tokio::sync::mpsc::Sender<ClientActorMessage>, | ||
| label: String, | ||
| ) -> Result<(), Error> { | ||
| debug!(label=%label, "calling set label"); |
There was a problem hiding this comment.
This debug log emits the full label value. Since labels are described as potentially containing user identifiers, this can leak PII into logs. Consider removing the label value from logs or logging only a redacted/hashed form (or just length).
| debug!(label=%label, "calling set label"); | |
| debug!(label_len = label.len(), "calling set label"); |
| /// Set an optional human-readable name for this endpoint, making metrics | ||
| /// from this endpoint easier to identify. This is often used for associating | ||
| /// with other services in your app, like a database user id. | ||
| /// | ||
| /// When this builder method is called, the provided label label is sent | ||
| /// after the client initially authenticates the endpoint server-side. | ||
| /// Errors will not interrupt client construction, instead producing a | ||
| /// warn-level log. For explicit error handling, use [`Client::set_label`]. | ||
| /// | ||
| /// labels can be any UTF-8 string, with a min length of 2 bytes, and | ||
| /// maximum length of 128 bytes. **label uniqueness is not enforced.** | ||
| pub fn label(mut self, label: impl Into<String>) -> Result<Self> { |
There was a problem hiding this comment.
PR description mentions examples using .name, but the public API added here is .label(...) and the example is updated accordingly. Please align the PR description (or rename the API) so the documentation and review context match the actual code.
| @@ -0,0 +1,8 @@ | |||
| { | |||
There was a problem hiding this comment.
lets not check these in please
| // 3. Build a Client that dials iroh-services (as in all other examples). | ||
| let client = Client::builder(&endpoint) | ||
| .api_secret(secret.clone())? | ||
| .label(label)? |
There was a problem hiding this comment.
I thought we had settled on name? using label is very confusing imho because I can only set a single one
There was a problem hiding this comment.
apologies, I missed that discussion. I can revert to use name. Label made more sense to me, but I don't care either way
There was a problem hiding this comment.
Yeah I started with label then we changed it to name; to be honest I don't care either way but I prefer name as well.
|
I was pretty much ready to approve this pr after the work from @okdistribute but the new commits seem to shuffle a lot of stuff making it more complicated and confusing for me. Is that necessary? |
|
I had to re-write this to make the changes non-breaking. Specifically so we can accept both versions of the |
did you? it was working fine before in my tests |
| let tx = tx.clone(); | ||
| tokio::spawn(async move { | ||
| if let Err(err) = set_name_inner(tx, name).await { | ||
| warn!(err = %err, "setting endpoint label on startup"); |
There was a problem hiding this comment.
maybe something like "failed setting.."
| if let Some(name) = &self.name { | ||
| let name = name.clone(); | ||
| let tx = tx.clone(); | ||
| tokio::spawn(async move { |
There was a problem hiding this comment.
I wish we didn't have this task untracked,
|
|
||
| #[tokio::test] | ||
| async fn test_name() { | ||
| let mut rng = rand::rng(); |
There was a problem hiding this comment.
do you mean, use the rand_chacha crate instead of rand? we already have dep on rand, I could switch both over, but feels outside of scope for this PR
| .map_err(|_| RemoteError::InternalServerError) | ||
| } | ||
|
|
||
| async fn send_name_endpoint(&mut self, label: String) -> Result<(), RemoteError> { |
There was a problem hiding this comment.
this is not updating the name on the actor, when after it is set
Description
This adds the capability to give endpoints a label so they are more easily identifiable in metrics & observability services such as https://services.iroh.computer/
Breaking Changes
No breaking changes.
This is acceptable because we don't want to break people's existing deployments, we already know ottomatic & nous have integrated this into their client apps.
This can be an opt-in feature but since the examples will all use
.name, it's likely most new developers will add a name. So we get the best of both, without restricting devs who want endpoints to remain anonymous.Notes & open questions
This does not need to be rolled out with any server changes because I've added a test to ensure it'll still work with older server versions. Hopefully this can be the case.
If we do need to roll out a server version first before publishing, we can do that but was trying to avoid the coupling of the two releases.
I asked copilot to review just to see what their changes suggested would be, and it was a disaster. Never using that again. Claude's review was much more accurate.
Change checklist