From 6dd6f632dfd8f401fa08080a0d52d50dd5f22eef Mon Sep 17 00:00:00 2001 From: Tyrie Vella Date: Wed, 30 Sep 2026 12:21:28 -0700 Subject: [PATCH] Add IsSendingAnonymousRequests to VFS.Heartbeat Expose whether outgoing gvfs.exe requests are sent without an Authorization header via the periodic VFS.Heartbeat event, sourced from the enlistment's existing public GitAuthentication.IsAnonymous property. Application Insights shows a fleet failure mode where mounts get stuck unable to download blob sizes or objects (SizesUnavailableException), affecting roughly 6% of active machines with no repro in hand. The suspected cause is an anonymous-auth latch: when the initial anonymous config probe comes back indeterminate, GitAuthentication never clears IsAnonymous, so HttpRequestor permanently omits the Authorization header. Today no heartbeat or telemetry field exposes this state, so the mechanism cannot be confirmed or sized in the field. This change is intentionally scoped away from GitAuthentication.cs and HttpRequestor.cs, which are being reworked by unrelated in-flight fixes for the same bug. The new field is named for the observable effect on the wire (no Authorization header sent) rather than for the internal property, and reads the same way regardless of which build emits it, even though IsAnonymous's default value before the first auth probe completes differs between the current code and the in-flight fix. Assisted-by: Claude Sonnet 5 Signed-off-by: Tyrie Vella --- .../Virtualization/FileSystemCallbacksTests.cs | 7 +++++-- GVFS/GVFS.Virtualization/FileSystemCallbacks.cs | 6 ++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/GVFS/GVFS.UnitTests/Virtualization/FileSystemCallbacksTests.cs b/GVFS/GVFS.UnitTests/Virtualization/FileSystemCallbacksTests.cs index 3a515c3b5..7ab7b13de 100644 --- a/GVFS/GVFS.UnitTests/Virtualization/FileSystemCallbacksTests.cs +++ b/GVFS/GVFS.UnitTests/Virtualization/FileSystemCallbacksTests.cs @@ -119,6 +119,7 @@ public void GetMetadataForHeartBeatDoesNotChangeEventLevelWhenNoPlaceholderHaveB metadata.ShouldContain("ModifiedPathsCount", 1); metadata.ShouldContain("FilePlaceholderCount", 0); metadata.ShouldContain(nameof(RepoMetadata.Instance.EnlistmentId), RepoMetadata.Instance.EnlistmentId); + metadata.ShouldContain("IsSendingAnonymousRequests", true); } mockPlaceholderDb.VerifyAll(); @@ -159,7 +160,7 @@ public void GetMetadataForHeartBeatDoesSetsEventLevelToInformationalWhenPlacehol eventLevel.ShouldEqual(EventLevel.Informational); // "ModifiedPathsCount" should be 1 because ".gitattributes" is always present - metadata.Count.ShouldEqual(8); + metadata.Count.ShouldEqual(9); metadata.ContainsKey("FilePlaceholderCreation").ShouldBeTrue(); metadata.TryGetValue("FilePlaceholderCreation", out object fileNestedMetadata); GVFSJsonOptions.Serialize(fileNestedMetadata).ShouldContain("\"ProcessName1\":\"GVFS.UnitTests.exe\""); @@ -169,6 +170,7 @@ public void GetMetadataForHeartBeatDoesSetsEventLevelToInformationalWhenPlacehol metadata.ShouldContain("FolderPlaceholderCount", 0); metadata.ShouldContain(nameof(RepoMetadata.Instance.EnlistmentId), RepoMetadata.Instance.EnlistmentId); metadata.ContainsKey("PhysicalDiskInfo").ShouldBeTrue(); + metadata.ShouldContain("IsSendingAnonymousRequests", true); // Create more placeholders fileSystemCallbacks.OnPlaceholderFileCreated("test.txt", "2222233333444445555566666777778888899999", "GVFS.UnitTests.exe2"); @@ -182,7 +184,7 @@ public void GetMetadataForHeartBeatDoesSetsEventLevelToInformationalWhenPlacehol eventLevel = writeToLogFile2 ? EventLevel.Informational : EventLevel.Verbose; eventLevel.ShouldEqual(EventLevel.Informational); - metadata.Count.ShouldEqual(8); + metadata.Count.ShouldEqual(9); // Only processes that have created placeholders since the last heartbeat should be named metadata.ContainsKey("FilePlaceholderCreation").ShouldBeTrue(); @@ -202,6 +204,7 @@ public void GetMetadataForHeartBeatDoesSetsEventLevelToInformationalWhenPlacehol metadata.ShouldContain("FolderPlaceholderCount", 1); metadata.ShouldContain(nameof(RepoMetadata.Instance.EnlistmentId), RepoMetadata.Instance.EnlistmentId); metadata.ContainsKey("PhysicalDiskInfo").ShouldBeTrue(); + metadata.ShouldContain("IsSendingAnonymousRequests", true); } mockPlaceholderDb.VerifyAll(); diff --git a/GVFS/GVFS.Virtualization/FileSystemCallbacks.cs b/GVFS/GVFS.Virtualization/FileSystemCallbacks.cs index 91e627dd2..c9fc1cd2a 100644 --- a/GVFS/GVFS.Virtualization/FileSystemCallbacks.cs +++ b/GVFS/GVFS.Virtualization/FileSystemCallbacks.cs @@ -292,6 +292,12 @@ public EventMetadata GetAndResetHeartBeatMetadata(out bool logToFile) this.context.Enlistment.WorkingDirectoryBackingRoot, sizeStatsOnly: true)); + // True/false means "HttpRequestor is/isn't attaching an Authorization + // header to outgoing requests" (see HttpRequestor.cs), so a mount stuck + // reporting true for long stretches against a server that requires auth + // is the signature of the anonymous-auth-latch bug this field detects. + metadata.Add("IsSendingAnonymousRequests", this.context.Enlistment.Authentication.IsAnonymous); + return metadata; }