Skip to content

 Send auth on cluster recommendations and report cluster source (C#, Go, TS) - #674

Merged
Arun G (ArunGopinathan) merged 4 commits into
mainfrom
agopinathan/reco-auth-and-diagnostics
Sep 1, 2026
Merged

 Send auth on cluster recommendations and report cluster source (C#, Go, TS) #674
Arun G (ArunGopinathan) merged 4 commits into
mainfrom
agopinathan/reco-auth-and-diagnostics

Conversation

@ArunGopinathan

@ArunGopinathan Arun G (ArunGopinathan) commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Changes proposed:

  • Send the caller's token on the cluster recommendations call (C#, Go; TypeScript already did), so the service can identify the caller and apply their service tier. The call was previously anonymous, so every caller was treated the same.
  • Retry anonymously if the token is rejected, so a single expired token cannot silently disable recommendation-based routing for that caller and drop them back to nearest-cluster selection.
  • Stop swallowing recommendation failures. createTunnel had an empty catch in all three SDKs, so a fallback to nearest-cluster routing left no trace. The outcome is now classified and reported in a new X-Tunnel-Cluster-Source request header: explicit, recommended, recommended-after-auth-rejected, fallback-auth-failed, fallback-empty, fallback-error.
  • Raise a client-side notification for embedders that want to log the same thing locally — a ClusterSelected event (C#), an OnClusterSelected callback field (Go), and an onClusterSelected emitter (TypeScript). Secondary; the header needs no embedder cooperation.
  • Bump Go PackageVersion to 0.1.28, and raise the TS management/connections dependency floors from >1.3.50 to >1.3.55 to match the published packages.

Behaviour is unchanged when recommendations succeed. The fallback path is the same as before — it is now attributed rather than silent. The header is ignored by the service until the corresponding service-side change finishes rolling out, so this is safe to ship independently.

Java and Rust are deliberately out of scope: their management clients never call the recommendations API, so there is no failure to attribute. Adopting recommendations there is a separate, larger change per language.

Testing

Each SDK has tests covering: the token being sent, no token sent when the caller is anonymous, the anonymous retry after a 401/403, no retry on a non-auth failure, and the header value for each of the six classifications.

  • C# — 144/144 pass
  • Go — 12 new tests pass (remaining suite failures are pre-existing token-gated integration tests)
  • TypeScript — 93/93 pass, eslint clean

Every new check was fault-injected to confirm it can fail.

Also validated end-to-end: the branch was packed as a prerelease package, consumed by the .NET devtunnel CLI, and pointed at a local stub that records requests. devtunnel create sends the recommendations call with an Authorization header and creates with X-Tunnel-Cluster-Source: recommended; devtunnel create <id>.<cluster> skips recommendations entirely and sends explicit. Confirmed with both Microsoft and GitHub sign-in. Running the same steps against the currently published package sends no Authorization header at all, which is the behaviour this fixes.

Other Tasks:

  • If you updated the Go SDK did you update the PackageVersion in tunnels.go
  • If you updated the TS SDK did you update the dependencies in package.json for connections and management to require a dependency that is > the current published version(Found using npm view @microsoft/dev-tunnels-contracts). This will fix issues where yarn will pull the old version of packages and will cause mismatched dependencies. See example PR

GetClusterRecommendationsAsync hardcoded authHeader: null, so the call was always
anonymous even when the same client authenticated the create that followed it.
The service therefore could not identify the caller, and every SDK caller resolved
to the default service tier regardless of configuration. Pass the token from the
existing userTokenCallback, which is what every other request already uses.

The service rejects an invalid or expired token before the controller runs, so it
does not degrade to treating the caller as anonymous - it returns 401. A rejected
token is now retried once without the header, so a token problem falls back to the
previous anonymous behaviour instead of losing recommendations entirely. Clients
with no token are unaffected: no header is sent and no retry is attempted.

CreateTunnelAsync caught every recommendation failure with an empty block. The
create still succeeds via global routing, so a caller had no way to tell that
recommendation-based placement had stopped working - it looks identical to normal
operation while silently reverting to nearest-by-latency selection. Failures are
now classified and reported two ways: a ClusterSelected event for embedders, and
an X-Tunnel-Cluster-Source header on the create so the path is visible in service
telemetry rather than only on the client.

The header requires a service-side change to be logged before it is observable.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 09709668-833e-4c9a-bea4-fdb93f91b8e5
Mirrors the C# change in the Go SDK.

The recommendations call was always anonymous, so the service could not identify
the caller or apply their service tier. It now sends the caller's token.

The service rejects a bad token before the controller runs rather than treating
the caller as anonymous, so a rejected token is retried once without it. Without
that retry a single expired token would silently disable recommendation-based
routing for that caller.

When the recommendations call fails, the create still succeeds via global
routing, so the fallback was invisible. The selection path is now reported two
ways: an X-Tunnel-Cluster-Source header, so it is visible in service telemetry
without any cooperation from the embedder, and an optional OnClusterSelected
callback for embedders that want to log it locally. The header values match the
C# ones exactly.

The header is passed explicitly to sendRequest because
TunnelRequestOptions.AdditionalHeaders is written in four places but never read
when the request is built; turning that on would also start sending If-Match and
If-Not-Match, which is a separate behaviour change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 09709668-833e-4c9a-bea4-fdb93f91b8e5
…cript)

The recommendations call now sends the caller token so the service can identify
them and apply their service tier. A rejected token is retried anonymously,
because the service rejects a bad token before the controller runs and would
otherwise silently disable recommendation-based routing for that caller.

createTunnel no longer swallows recommendation failures in an empty catch. It
classifies the outcome and sends it in the X-Tunnel-Cluster-Source header, so a
silent fallback to Traffic Manager routing is visible service-side, and raises
onClusterSelected for embedders that want to log it.

Matches the C# and Go changes on this branch.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 09709668-833e-4c9a-bea4-fdb93f91b8e5
Required by the PR checklist. The Go PackageVersion goes to 0.1.28.

The TS management and connections manifests still required >1.3.50 while the
published packages are at 1.3.55, so yarn could resolve an older contracts or
management package than the one these changes are built against and produce a
mismatched dependency set.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 09709668-833e-4c9a-bea4-fdb93f91b8e5
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@ArunGopinathan Arun G (ArunGopinathan) changed the title  Send auth on cluster recommendations and report cluster source (C#, Go, TS)  Send auth on cluster recommendations and report cluster source (C#, Go, TS) -no merge yet. server side telemetry need to rollout first Aug 31, 2026
@ArunGopinathan Arun G (ArunGopinathan) changed the title  Send auth on cluster recommendations and report cluster source (C#, Go, TS) -no merge yet. server side telemetry need to rollout first  Send auth on cluster recommendations and report cluster source (C#, Go, TS) - NO MERGE YET. server side telemetry need to rollout first Aug 31, 2026
@ArunGopinathan Arun G (ArunGopinathan) changed the title  Send auth on cluster recommendations and report cluster source (C#, Go, TS) - NO MERGE YET. server side telemetry need to rollout first  Send auth on cluster recommendations and report cluster source (C#, Go, TS) Sep 1, 2026
@ArunGopinathan

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).

@ArunGopinathan
Arun G (ArunGopinathan) merged commit 802b6bb into main Sep 1, 2026
11 checks passed
@ArunGopinathan
Arun G (ArunGopinathan) deleted the agopinathan/reco-auth-and-diagnostics branch September 1, 2026 23:42
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.

2 participants