fix: populate OTel repository attributes for non-GitHub git remotes - #330284
Open
samir-nimbly wants to merge 2 commits into
Open
fix: populate OTel repository attributes for non-GitHub git remotes#330284samir-nimbly wants to merge 2 commits into
samir-nimbly wants to merge 2 commits into
Conversation
`github.copilot.git.repository`, `copilot_chat.repo.remote_url`, and `github.copilot.github.org` were silently absent on `invoke_agent` spans for workspaces whose remote points at a self-hosted GitHub Enterprise Server instance on a custom domain, while `github.copilot.git.branch` and `github.copilot.git.commit_sha` were present on the same span. GitLab, Bitbucket Server, and plain git servers were affected the same way. `buildWorkspaceMetadata` derived the remote URL exclusively from `getOrderedRepoInfosFromContext`, which only yields remotes that resolve to a repo id via the `['github.com', 'ghe.com']` allowlist or the Azure DevOps hosts. Branch and commit are read straight off `RepoContext` with no host check, hence the asymmetry. Add a `pickRemoteUrl` helper that prefers a remote resolving to a repo id, so repositories that already reported a URL keep reporting the same one, and only falls back to the highest-priority remote of any host when nothing resolves. The fallback requires `parseRemoteUrl` to succeed, which admits only ssh, https and http, keeping local remotes (`file://`, absolute and relative paths) out of telemetry. Rewrite `extractGitHubOrg` to delegate to `getGithubRepoIdFromFetchUrl` instead of a `github.com`-only regex. This keeps the org attribute scoped to recognized GitHub hosts while recovering it for `*.ghe.com` and ssh host aliases, which the regex previously dropped. It remains absent for custom-domain GitHub Enterprise Server, which cannot be distinguished from an arbitrary git host without reading `github-enterprise.uri`. Also fix an unrelated latent crash surfaced by the new tests: strategy 1 of `getOrderedRemoteUrlsFromContext` asserted `remoteFetchUrls[0]!`, but a push-only remote carries no fetch URL, so `undefined` escaped the declared `Iterable<string>` and threw inside `parseRemoteUrl`. The repo id allowlist, `getOrderedRepoInfosFromContext`, and every consumer that depends on repo identity for GitHub API calls are left untouched. Fixes microsoft#330239 Co-authored-by: Mohammed Samir <51956976+SamirSaji@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes repository metadata for non-GitHub remotes in Copilot OTel spans while preserving existing remote priority.
Changes:
- Adds host-agnostic remote URL fallback and GitHub organization resolution.
- Handles missing fetch URLs safely.
- Expands tests and monitoring documentation.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
workspaceOTelMetadata.ts |
Selects and normalizes arbitrary-host remotes. |
workspaceOTelMetadata.spec.ts |
Tests remote selection and OTel attributes. |
genAiAttributes.ts |
Updates repository attribute documentation. |
gitService.ts |
Guards missing lone-remote fetch URLs. |
gitService.spec.ts |
Tests remote ordering and missing URLs. |
agent_monitoring.md |
Documents GitHub organization availability. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Scope the host-agnostic remote to OTel emission. `WorkspaceOTelMetadata` now carries both `remoteUrl` (any host, for OTel attributes) and `recognizedRemoteUrl` (restricted to remotes resolving to a repo id). The two consumers that forward a remote to GitHub telemetry rather than to the user's own OTel exporter — `inline.trackEditSurvival` in userActions and `fastApply/editOutcome` in MultiFileEditInternalTelemetryService — now read `recognizedRemoteUrl`, so those channels collect exactly what they collected before and the OTel fix carries no privacy expansion. Reject loopback hosts in the fallback. `parseRemoteUrl` admits ssh, https, and http, so scp-style syntax could smuggle a local filesystem path through an accepted scheme (`git@localhost:/Users/alice/dev/repo.git`). Loopback hosts (localhost, *.localhost, 127.0.0.0/8, ::1, 0.0.0.0) are now skipped. Absolute paths on routable hosts are still reported, since those describe the server's layout rather than the user's. Document that ssh host aliases normalizing to github.com or *.ghe.com also produce `github.copilot.github.org`, in both the attribute reference and the monitoring docs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #330239
Problem
On Copilot Chat
invoke_agentspans, three OTel attributes were silently absent whenever the workspace's remote pointed at a self-hosted GitHub Enterprise Server instance on a custom domain (e.g.https://git.mycompany.com/owner/repo.git):github.copilot.git.repositorycopilot_chat.repo.remote_urlgithub.copilot.github.orgMeanwhile
github.copilot.git.branchandgithub.copilot.git.commit_shawere present and correct on the same span. No error, no log line — the attributes just never appeared, so anyone pointing an OTLP collector at their agent turns lost repository correlation entirely.The published monitoring docs state the repository attribute is emitted "when in a Git repo", so this was a documented-contract violation. GitLab, Bitbucket Server, and plain git servers were affected identically.
Root cause
All five attributes are produced by
extensions/copilot/src/platform/otel/common/workspaceOTelMetadata.ts.buildWorkspaceMetadatareadheadBranchNameandheadCommitHashstraight offRepoContextwith no host check, but derivedremoteUrlexclusively from:getOrderedRepoInfosFromContext(platform/git/common/gitService.ts) yields a remote only whengetGithubRepoIdFromFetchUrl(url) ?? getAdoRepoIdFromFetchUrl(url)resolves.getGithubRepoIdFromFetchUrlhard-gates onconst topLevelUrls = ['github.com', 'ghe.com'], and the Azure DevOps resolver only handlesdev.azure.com,ssh.dev.azure.com, and*.visualstudio.com.A custom GHES domain matches neither allowlist, so the generator yielded nothing,
remoteUrlstayedundefined, and theif (metadata.remoteUrl)branch inworkspaceMetadataToOTelAttributesnever ran — dropping all three attributes at once. That is exactly the asymmetry described in the issue.Secondary defect:
extractGitHubOrgmatched only/github\.com[/:]([^/]+)\/[^/]+\/?$/i, so it dropped the org even for*.ghe.comtenants and ssh host aliases (abc-github.com) thatgetGithubRepoIdFromFetchUrlalready resolves correctly.Changes
1. Host-agnostic remote URL, without changing any existing value
Two non-exported helpers.
pickRecognizedRemoteUrlreturns the highest-priority remote that resolves to a repo id;pickFallbackRemoteUrlreturns the highest-priority remote on any host. The resolvable one is preferred:This ordering matters. A naive host-agnostic rewrite regresses mixed-remote repositories: with
originpointing at a GitLab mirror and a second remote at GitHub, the old code reported the GitHub URL (plusgithub.org), and a first-parseable-wins implementation would silently switch that span to the GitLab URL and drop the org. Preferring the resolvable remote makes the change strictly additive — no repository that already reported a URL sees a different value. Three tests lock this behaviour.Both passes reuse
getOrderedRemoteUrlsFromContext, so remote prioritization (a lone remote → the upstream remote →origin→ the rest) is unchanged.2. Local remotes excluded from the fallback
Removing the host gate means arbitrary remote strings become eligible, including
file:///Users/<name>/dev/repoand relative paths, whichnormalizeFetchUrlreturns verbatim from itscatchbranch. The old allowlist suppressed those incidentally.The fallback requires
parseRemoteUrl(url)to resolve, which admits onlyssh,https, andhttp. That alone is not sufficient, because scp-style syntax can smuggle a local path through an accepted scheme —git@localhost:/Users/alice/dev/repo.gitnormalizes tossh://git@localhost//Users/alice/dev/repo.gitand passes the scheme check. Loopback hosts (localhost,*.localhost,127.0.0.0/8,::1,0.0.0.0) are therefore rejected on top of the scheme check.Absolute paths on routable hosts are still reported:
git@git.mycompany.com:/srv/git/repo.gitis a normal self-hosted layout where the path describes the server's filesystem, not the user's.3. The host-agnostic remote is scoped to OTel emission
WorkspaceOTelMetadatacarries two remote values:remoteUrl— any host. Used for the OTel attributes; this is what fixes the issue.recognizedRemoteUrl— only remotes resolving to a repo id (github.com,*.ghe.com, Azure DevOps), i.e. exactly the set reportable before this PR.Two consumers forward a remote to GitHub telemetry rather than to the user's own OTel exporter, and both now read
recognizedRemoteUrlso their payloads are unchanged frommain:extension/conversation/vscode-node/userActions.ts→sendGHTelemetryEvent('inline.trackEditSurvival', ...)platform/multiFileEdit/common/multiFileEditQualityTelemetry.ts→sendEnhancedGHTelemetryEvent('fastApply/editOutcome', ...)A field split is used rather than an opt-in flag on
resolveWorkspaceOTelMetadatabecauseEditSurvivalReporterresolves a single metadata object that feeds bothemitEditSurvivalEvent(OTel) and the GitHub event; a per-call flag would have forced the OTel path to lose the fix there too.4.
extractGitHubOrgdelegates to the canonical resolverReuses the allowlist read-only. This keeps the org attribute scoped to recognized GitHub hosts (preserving the existing
omits github.org for non-github remotestest) while recovering the org for*.ghe.comtenants and ssh host aliases that the previous regex dropped.5. Pre-existing crash, surfaced by the new tests
Adding a
remoteFetchUrls: [undefined]case produced:Strategy 1 of
getOrderedRemoteUrlsFromContextassertedout.add(repoContext.remoteFetchUrls[0]!), but a push-only remote carries no fetch URL, soundefinedescaped the declaredIterable<string>and threw. This predates this PR and also reachesextension/prompt/node/repoInfoTelemetry.ts. Fixed at source with a truthy guard; strategies 2, 3, and the trailing loop already guarded correctly.6. Documentation
docs/monitoring/agent_monitoring.md— thegithub.copilot.github.orgrow now names the full recognized set (github.com,*.ghe.com, and ssh host aliases normalizing to either) and notes it is absent for custom-domain GHES.genAiAttributes.ts— theGITHUB_ORGcomment said "gated like the URL itself"; the URL is no longer gated, so it now describes the actual condition, including ssh host aliases.extractGitHubOrgJSDoc carried the same omission and is corrected to match.Explicitly out of scope
The repo id allowlist is untouched.
getGithubRepoIdFromFetchUrl,getAdoRepoIdFromFetchUrl, andgetOrderedRepoInfosFromContextkeep their exact current semantics. They feed GitHub API calls, PR creation, the coding agent, andrepoId/repoTypetelemetry across roughly fifteen call sites; returning aGithubRepoIdfor a GitLab host would be wrong and would change outbound API behaviour. The fix is confined to the OTel metadata path.github.copilot.github.orgremains absent for custom-domain GHES. Distinguishing a self-hosted GHES domain from an arbitrary git host requires reading thegithub-enterprise.urisetting.platform/otel/common/currently has no dependency injection, and threadingIConfigurationServicethrough would mean constructor churn at call sites that do not inject it (editSurvivalReporter,multiFileEditQualityTelemetry,userActions) for a single telemetry string. Sincegithub.copilot.git.repositoryis now always populated, a backend can derive the org from the URL. A test documents this limitation explicitly. Happy to follow up if maintainers would prefer the attribute plumbed through.The following are the same class of bug in core, filed separately rather than folded in here:
src/vs/workbench/contrib/chat/browser/chatRepoInfo.tsclassifies*.ghe.comasremoteVendor: 'other'and omitsssh.dev.azure.com, disagreeing withsrc/vs/platform/agentHost/node/agentHostRepoInfoTelemetry.ts.src/vs/workbench/contrib/git/common/utils.tshasGitHubRemotesuses an un-dot-anchoredendsWith('github.com'), soevilgithub.compasses. The function currently has no callers.src/extension/inlineChat2/node/inlineChatIntent.tscreates aninvoke_agent Inline Chatspan that setsAGENT_TYPEbut never spreads the workspace git attributes at all.Tests
20 new cases.
src/platform/otel/common/test/workspaceOTelMetadata.spec.ts:git@host:owner/repo.gitnormalizes tohttps://host/owner/repo.git/scm/path/scm/segment stripped bynormalizeFetchUrlgithub.comtoContainto an exact match)file://, absolute path, relative pathssh://,127.0.0.1,*.localhost, https with port,[::1]recognizedRemoteUrlfor github.com / Azure DevOps / custom GHESundefinedrecognizedRemoteUrlin a mixed-remote repoundefined, no throw*.ghe.comgithub.orgnow derived (previously dropped)github.orgderivedgithub.orgabsent — documents the limitationgithub.orgabsentsrc/platform/git/test/node/gitService.spec.tsadds direct coverage forgetOrderedRemoteUrlsFromContext(lone remote with and without a fetch URL, upstream ordering) andgetOrderedRepoInfosFromContext(no throw on a missing fetch URL, unknown hosts skipped).Verification
npx vitest --run --pool=forks src/platform/otel src/platform/git src/platform/multiFileEdit src/platform/editSurvivalTrackingnpm run typecheck(all four tsconfig projects)npx eslinton all changed filesnpm run test:unitvs. stashed baselineThe 39 pre-existing failures are environmental in this checkout — they need
dist/*.wasmtree-sitter builds and a seeded SQLite model-metadata cache. The stashed-baseline run confirms none of them are attributable to this change.Not performed locally: an end-to-end run against a live GHES remote with
"github.copilot.chat.otel.enabled": trueand a real Copilot session.Notes for reviewers
Two behavioural consequences worth a deliberate look:
git.mycompany.com) now appear where they previously did not, but only on spans exported to the OTLP endpoint the user configured. Following review, the two consumers that forward a remote to GitHub telemetry (inline.trackEditSurvivalandfastApply/editOutcome) readrecognizedRemoteUrland so collect exactly what they collected onmain.github.copilot.git.repositorymoves from GitHub/ADO hosts only to every host, so distinct-value cardinality on that attribute will rise. Worth a look from whoever owns OTel backend cost.Local-path exposure is addressed by the loopback rejection described above. Credential exposure is unchanged:
normalizeFetchUrlrebuilds fromhostname + pathname, dropping userinfo, port, and query; the scp branch drops the user; and auser:pass@form does not match the scp regex, so it goes down the credential-strippingnew URL()path.