Skip to content

feat(client): add optional name for endpoints#71

Merged
dignifiedquire merged 29 commits into
mainfrom
rae/endpoint-labels
Apr 11, 2026
Merged

feat(client): add optional name for endpoints#71
dignifiedquire merged 29 commits into
mainfrom
rae/endpoint-labels

Conversation

@okdistribute
Copy link
Copy Markdown
Contributor

@okdistribute okdistribute commented Mar 11, 2026

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

  • Self-review.
  • Documentation updates following the style guide, if relevant.
  • Tests if relevant.
  • All breaking changes documented.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 11, 2026

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

@okdistribute okdistribute changed the title Endpoint labels feat: add optional label for endpoints Mar 11, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 Auth protocol message with an optional label: Option<String>.
  • Add a ClientBuilder::label(...) API and plumb the value into the client actor’s Auth RPC.
  • 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.

Comment thread src/client.rs Outdated
Comment thread src/protocol.rs
Comment thread src/protocol.rs Outdated
Comment thread src/protocol.rs Outdated
@okdistribute okdistribute changed the title feat: add optional label for endpoints feat: add optional name for endpoints Mar 11, 2026
@dignifiedquire
Copy link
Copy Markdown
Contributor

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

@okdistribute
Copy link
Copy Markdown
Contributor Author

@dignifiedquire yes, I had bumped semver to be a breaking change, looks like you moved it back down? 165238c#diff-2e9d962a08321605940b5a657135052fbcef87b5e360662bb527c96d9a615542R3

@dignifiedquire dignifiedquire changed the title feat: add optional name for endpoints feat!: add optional name for endpoints Mar 23, 2026
@b5 b5 force-pushed the rae/endpoint-labels branch from 610ba5f to c6ede8c Compare March 23, 2026 19:49
@b5 b5 changed the title feat!: add optional name for endpoints feat!(client): add optional label for endpoints Mar 23, 2026
okdistribute and others added 12 commits March 23, 2026 16:00
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>
@b5 b5 changed the title feat!(client): add optional label for endpoints feat(client): add optional label for endpoints Mar 23, 2026
@b5 b5 force-pushed the rae/endpoint-labels branch from 5a9c89e to 52e6f88 Compare March 23, 2026 22:38
@b5
Copy link
Copy Markdown
Member

b5 commented Mar 23, 2026

fml that was a pain, but I've implemented this as a non-breaking set of changes.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

Comment thread .claude/settings.local.json Outdated
Comment on lines +2 to +7
"permissions": {
"allow": [
"mcp__acp__Edit",
"mcp__acp__Bash"
]
}
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
"permissions": {
"allow": [
"mcp__acp__Edit",
"mcp__acp__Bash"
]
}

Copilot uses AI. Check for mistakes.
Comment thread src/client.rs Outdated
Comment on lines +289 to +291
#[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).")]
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
#[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).")]

Copilot uses AI. Check for mistakes.
Comment thread src/client.rs Outdated

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");
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
panic!("label should fail for strings over 1024 bytes");
panic!("label should fail for strings over 128 bytes");

Copilot uses AI. Check for mistakes.
Comment thread Cargo.toml Outdated
[package]
name = "iroh-services"
version = "0.12.0"
version = "0.11.0"
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
version = "0.11.0"
version = "0.12.0"

Copilot uses AI. Check for mistakes.
Comment thread src/client.rs Outdated
/// 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
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

Doc typo: "the provided label label" has a duplicated word. Please fix to avoid confusion in the public API docs.

Suggested change
/// When this builder method is called, the provided label label is sent
/// When this builder method is called, the provided label is sent

Copilot uses AI. Check for mistakes.
Comment thread src/client.rs Outdated
self.message_channel
.send(ClientActorMessage::ReadLabel { done: tx })
.await
.map_err(|_| Error::Other(anyhow!("sending ping request")))?;
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

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").

Suggested change
.map_err(|_| Error::Other(anyhow!("sending ping request")))?;
.map_err(|_| Error::Other(anyhow!("sending label read request")))?;

Copilot uses AI. Check for mistakes.
Comment thread src/client.rs Outdated
///
/// 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> {
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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
)));
}

Copilot uses AI. Check for mistakes.
Comment thread src/client.rs Outdated
}
}
ClientActorMessage::LabelEndpoint{ label, done } => {
let res = self.send_label_endpoint(label).await;
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
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);
}

Copilot uses AI. Check for mistakes.
Comment thread src/client.rs Outdated
message_channel: tokio::sync::mpsc::Sender<ClientActorMessage>,
label: String,
) -> Result<(), Error> {
debug!(label=%label, "calling set label");
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

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).

Suggested change
debug!(label=%label, "calling set label");
debug!(label_len = label.len(), "calling set label");

Copilot uses AI. Check for mistakes.
Comment thread src/client.rs Outdated
Comment on lines +115 to +126
/// 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> {
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment thread .claude/settings.local.json Outdated
@@ -0,0 +1,8 @@
{
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lets not check these in please

Comment thread examples/net_diagnostics.rs Outdated
// 3. Build a Client that dials iroh-services (as in all other examples).
let client = Client::builder(&endpoint)
.api_secret(secret.clone())?
.label(label)?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I thought we had settled on name? using label is very confusing imho because I can only set a single one

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

apologies, I missed that discussion. I can revert to use name. Label made more sense to me, but I don't care either way

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

@dignifiedquire
Copy link
Copy Markdown
Contributor

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?

@b5
Copy link
Copy Markdown
Member

b5 commented Apr 7, 2026

I had to re-write this to make the changes non-breaking. Specifically so we can accept both versions of the iroh-services crate

@dignifiedquire
Copy link
Copy Markdown
Contributor

I had to re-write this to make the changes non-breaking. Specifically so we can accept both versions of the iroh-services crate

did you? it was working fine before in my tests

@b5 b5 force-pushed the rae/endpoint-labels branch from 5395400 to 2be4508 Compare April 9, 2026 20:47
Comment thread src/client.rs Outdated
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");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

maybe something like "failed setting.."

Comment thread src/client.rs Outdated
if let Some(name) = &self.name {
let name = name.clone();
let tx = tx.clone();
tokio::spawn(async move {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wish we didn't have this task untracked,

Comment thread src/client.rs
Comment thread src/client.rs
Comment thread src/client.rs Outdated

#[tokio::test]
async fn test_name() {
let mut rng = rand::rng();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

rand_chacha please

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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

Comment thread src/client.rs Outdated
.map_err(|_| RemoteError::InternalServerError)
}

async fn send_name_endpoint(&mut self, label: String) -> Result<(), RemoteError> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this is not updating the name on the actor, when after it is set

@dignifiedquire dignifiedquire changed the title feat(client): add optional label for endpoints feat(client): add optional name for endpoints Apr 11, 2026
@dignifiedquire dignifiedquire merged commit c716b3b into main Apr 11, 2026
24 of 25 checks passed
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.

4 participants