From ca1947608ebb3673e0034652fc266f4c2d720833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Mon, 29 Jun 2026 10:52:40 +0200 Subject: [PATCH 01/87] Branding as 18.10.0 (#16189) --- eng/Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/Versions.props b/eng/Versions.props index a095b1129a..1cd14ac9f8 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -14,7 +14,7 @@ from appending +, which breaks DTAAgent. --> false - 18.9.0 + 18.10.0 preview From 8dd1944a85e0833dd7b44877a21614cdafedbc53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Mon, 29 Jun 2026 14:54:54 +0200 Subject: [PATCH 02/87] Remove Windows-Review from cross-platform tests (#16113) * Remove Windows-Review restriction from cross-platform tests Remove [TestCategory("Windows-Review")] from ~45 tests that don't use Windows-specific APIs or features. These tests were incorrectly marked as Windows-only. Changed test files (22 files): - DotnetTestMSBuildOutputTests.cs (3 tests) - use InvokeDotnetTest - LoggerTests.cs (6 tests) - TRX/HTML logger validation - SerializationCompatibilityTests.cs (4 tests) - JSON serialization - ExecutionTests.cs (7 of 10 tests) - cross-platform execution - ArgumentProcessorTests.cs (3 tests) - CLI validation - DiscoveryTests.cs (2 tests) - test discovery - FrameworkTests.cs (2 tests) - framework selection - TranslationLayerTests (8 tests across 8 files) - API tests - vstest.console.UnitTests (4 tests across 3 files) - unit tests - And 3 more single-test files Kept Windows-Review on legitimately platform-specific tests: - ExecutionTests.cs: 3 x86/x64 architecture-specific tests - All EventLogCollectorTests, BlameDataCollectorTests, etc. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Keep Windows-Review on AeDebugger tests AeDebug is a Windows-only feature and these tests rely on Windows path semantics (c:\...), so they fail on Linux/macOS. Restore the category that was wrongly removed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Keep Windows-Review on integration tests that need the .NET Framework runner These tests use the compatibility/wrapper matrix data sources (Runner/TestHost/MSTest/WrapperCompatibilityDataSource) which emit .NET Framework runner+testhost combinations on every OS, unlike NetFullTargetFrameworkDataSource which self-gates net48 rows behind isWindows. Without Windows-Review they run on Linux/macOS where the desktop runner doesn't exist and fail (143 row-failures across the two integration projects). Also re-gated two net(core) tests that genuinely differ on non-Windows: the MSBuildLogger special-char round-trip and the framework-incompatible warning. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Keep Windows-Review on RunTestsWithXunitAdapter (fails on Linux) The xUnit adapter returns no results on Linux (the diagnostic log shows a NullReferenceException because path is null), so the run yields an empty sequence and .First() throws 'Sequence contains no elements'. It passed on macOS and on an earlier ubuntu run, so it's flaky, but the original Windows-Review gating was correct. Restored it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Make tests with empty data sources fail instead of masking them ConsiderEmptyDataSourceAsInconclusive made MSTest treat a data source that produces no rows as inconclusive instead of failing. NetFullTargetFrameworkDataSource produces 0 rows on non-Windows, so dropping Windows-Review from those tests did not make them run cross-platform - it made them silently pass as inconclusive. Remove the setting from both integration test .runsettings so an empty data source fails loudly again. Move the tests that are genuinely cross-platform to the NetCore testhost so they produce rows on every platform: argument processor help, execution exit codes, runtime provider discovery and execution, trx and html loggers, and fully qualified discovery. Put Windows-Review back on the tests that really need the .NET Framework runner: MSTest v1 adapter, non-dll adapter, and full framework assembly loading. Also mark the pre-existing NetFull-only tests that the setting was masking (CreateNoNewWindow, LiveUnitTesting) as Windows-Review, otherwise they would start failing on Linux now that empty data sources are no longer hidden. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Re-add Windows-Review to the .NET Framework-only tests the audit missed Removing ConsiderEmptyDataSourceAsInconclusive makes MSTest fail on an empty data source instead of silently going inconclusive. That exposes every test whose data sources yield zero rows on Linux/macOS, not only the plain NetFull ones from the first pass. A second sweep of both integration test projects covering all four OS-empty vectors - NetFullTargetFrameworkDataSource, NetFrameworkRunner, NetCoreRunner with net4x-only TFMs (NETFX = net481), and NetCoreTargetFrameworkDataSource(useCoreRunner: false) - turned up four more genuinely Framework-bound tests: - MultitargetingTestHostTests: multitargets a net481 testhost - SerializerSelectionTests.OnNetFrameworkRunner_ShouldUseJsonite: the Framework runner's Jsonite serializer; the core side is already covered - FrameworkTests.OnWrongFrameworkPassedTestRunShouldNotRun: the only assertion runs on the desktop runner - RecursiveResourcesLookupTests: mscorlib resource crash repro, .NET Framework only (and currently ignored) None of these run cross-platform, so categorize them rather than move them to the core testhost. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove the empty .NET Framework data source from cross-platform tests MSTest validates each ITestDataSource on a method individually, so a test carrying both NetFullTargetFrameworkDataSource and NetCoreTargetFrameworkDataSource fails on Linux/macOS once the empty-data masking is gone: NetFull produces no rows off Windows, and that empty source is now a hard failure rather than inconclusive. Drop the redundant NetFull source from the tests that don't validate .NET Framework testhost behaviour - their .NET (net11.0) rows already ran on Linux. Keep NetFull and mark Windows-Review on the four tests that assert .NET Framework-specific behaviour (stack overflow / unhandled exception messages, and source navigation across both testhosts), since that coverage genuinely needs the Windows-only .NET Framework testhost. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../.runsettings | 3 --- .../ArgumentProcessorTests.cs | 7 ++---- .../CreateNoNewWindowTests.cs | 6 +++++ .../DataCollectionTests.cs | 4 +--- .../DiscoveryTests.cs | 9 ++----- .../DotnetTestMSBuildOutputTests.cs | 6 +---- .../ExecutionTests.cs | 24 +++++++++---------- .../FilePatternParserTests.cs | 4 ---- .../FrameworkTests.cs | 3 +-- .../LoggerTests.cs | 22 +++++------------ .../MultitargetingTestHostTests.cs | 2 ++ .../PortableNugetPackageTests.cs | 2 -- .../RecursiveResourcesLookupTests.cs | 3 ++- .../ResultsDirectoryTests.cs | 2 -- .../RunsettingsTests.cs | 2 -- .../SerializationCompatibilityTests.cs | 4 ++++ .../SerializerSelectionTests.cs | 3 +++ .../TelemetryTests.cs | 2 -- .../TestCaseFilterTests.cs | 10 -------- .../.runsettings | 3 --- .../FilterSourceIntegrationTests.cs | 1 - .../CustomTestHostLauncherTests.cs | 2 ++ .../DataCollectorAttachmentProcessor.cs | 1 + .../DifferentTestFrameworkSimpleTests.cs | 5 ++-- .../TranslationLayerTests/DiscoverTests.cs | 5 +++- .../LiveUnitTestingTests.cs | 2 ++ .../TranslationLayerTests/RunTests.cs | 8 ++++--- .../RunTestsWithFilterTests.cs | 1 + .../SerializeTestRunTests.cs | 5 ---- .../TargetFrameworkTestHostDemultiplexer.cs | 5 ---- .../AeDebuggerArgumentProcessorTest.cs | 1 + .../EnableBlameArgumentProcessorTests.cs | 2 -- ...tnetVStestMessageArgumentProcessorTests.cs | 1 - 33 files changed, 61 insertions(+), 99 deletions(-) diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/.runsettings b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/.runsettings index 823b5bb2d5..cd8b6eb4da 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/.runsettings +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/.runsettings @@ -1,5 +1,2 @@ - - true - \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ArgumentProcessorTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ArgumentProcessorTests.cs index 0f296846c5..8f2fe25d03 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ArgumentProcessorTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ArgumentProcessorTests.cs @@ -7,13 +7,11 @@ namespace Microsoft.TestPlatform.AcceptanceTests; [TestClass] -[TestCategory("Windows-Review")] public class ArgumentProcessorTests : AcceptanceTestBase { [TestMethod] - [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] + [NetCoreTargetFrameworkDataSource] public void PassingNoArgumentsToVsTestConsoleShouldPrintHelpMessage(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -34,8 +32,7 @@ public void PassingNoArgumentsToVsTestConsoleShouldPrintHelpMessage(RunnerInfo r } [TestMethod] - [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] + [NetCoreTargetFrameworkDataSource] public void PassingInvalidArgumentsToVsTestConsoleShouldNotPrintHelpMessage(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CreateNoNewWindowTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CreateNoNewWindowTests.cs index f7acd3b94a..5122bec933 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CreateNoNewWindowTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CreateNoNewWindowTests.cs @@ -12,6 +12,8 @@ namespace Microsoft.TestPlatform.AcceptanceTests; public class CreateNoNewWindowTests : AcceptanceTestBase { [TestMethod] + // CreateNoNewWindow maps to the Windows-only process CreateNoWindow flag and only runs on the .NET Framework testhost. + [TestCategory("Windows-Review")] [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: false)] public void WhenCreateNoNewWindowIsFalse_DiagShowsCreateNoWindowFalse(RunnerInfo runnerInfo) { @@ -34,6 +36,8 @@ public void WhenCreateNoNewWindowIsFalse_DiagShowsCreateNoWindowFalse(RunnerInfo } [TestMethod] + // CreateNoNewWindow maps to the Windows-only process CreateNoWindow flag and only runs on the .NET Framework testhost. + [TestCategory("Windows-Review")] [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: false)] public void WhenCreateNoNewWindowIsTrue_DiagShowsCreateNoWindowTrue(RunnerInfo runnerInfo) { @@ -56,6 +60,8 @@ public void WhenCreateNoNewWindowIsTrue_DiagShowsCreateNoWindowTrue(RunnerInfo r } [TestMethod] + // CreateNoNewWindow maps to the Windows-only process CreateNoWindow flag and only runs on the .NET Framework testhost. + [TestCategory("Windows-Review")] [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: false)] public void WhenCreateNoNewWindowIsNotSet_DefaultIsTrue(RunnerInfo runnerInfo) { diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectionTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectionTests.cs index 26e068f9cb..af0b35edcc 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectionTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectionTests.cs @@ -20,7 +20,6 @@ namespace Microsoft.TestPlatform.AcceptanceTests; public class DataCollectionTests : AcceptanceTestBase { [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void ExecuteTestsWithDataCollection(RunnerInfo runnerInfo) { @@ -44,7 +43,6 @@ public void ExecuteTestsWithDataCollection(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void ExecuteTestsWithDataCollectionUsingCollectArgument(RunnerInfo runnerInfo) { @@ -80,6 +78,7 @@ public void DataCollectorAssemblyLoadingShouldNotThrowErrorForNetCore(RunnerInfo } [TestMethod] + // .NET Framework testhost-specific assembly loading; not applicable to the netcore testhost. [TestCategory("Windows-Review")] [NetFullTargetFrameworkDataSource] public void DataCollectorAssemblyLoadingShouldNotThrowErrorForFullFramework(RunnerInfo runnerInfo) @@ -93,7 +92,6 @@ public void DataCollectorAssemblyLoadingShouldNotThrowErrorForFullFramework(Runn } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void DataCollectorAttachmentProcessor(RunnerInfo runnerInfo) { diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiscoveryTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiscoveryTests.cs index 3680b3c9bc..23d047a76f 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiscoveryTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiscoveryTests.cs @@ -18,7 +18,6 @@ namespace Microsoft.TestPlatform.AcceptanceTests; public class DiscoveryTests : AcceptanceTestBase { [TestMethod] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] [NetCoreTargetFrameworkDataSource] public void DiscoverAllTests(RunnerInfo runnerInfo) { @@ -32,7 +31,6 @@ public void DiscoverAllTests(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true, useVsixRunner: true)] [NetCoreTargetFrameworkDataSource] [TestCategory("Smoke")] public void MultipleSourcesDiscoverAllTests(RunnerInfo runnerInfo) @@ -55,8 +53,7 @@ public void MultipleSourcesDiscoverAllTests(RunnerInfo runnerInfo) } [TestMethod] - [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] + [NetCoreTargetFrameworkDataSource] public void DiscoverFullyQualifiedTests(RunnerInfo runnerInfo) { var dummyFilePath = Path.Combine(TempDirectory.Path, $"{Guid.NewGuid()}.txt"); @@ -74,7 +71,6 @@ public void DiscoverFullyQualifiedTests(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void DiscoverTestsShouldShowProperWarningIfNoTestsOnTestCaseFilter(RunnerInfo runnerInfo) { @@ -119,8 +115,7 @@ public void TypesToLoadAttributeTests() } [TestMethod] - [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] + [NetCoreTargetFrameworkDataSource] public void DiscoverTestsShouldSucceedWhenAtLeastOneDllFindsRuntimeProvider(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestMSBuildOutputTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestMSBuildOutputTests.cs index 64634bde47..7602917081 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestMSBuildOutputTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestMSBuildOutputTests.cs @@ -16,7 +16,7 @@ namespace Microsoft.TestPlatform.AcceptanceTests; public class DotnetTestMSBuildOutputTests : AcceptanceTestBase { [TestMethod] - // patched dotnet is not published on non-windows systems + // Special characters (~, !, |, %) don't survive the MSBuildLogger output round-trip on non-Windows terminals. [TestCategory("Windows-Review")] [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] public void MSBuildLoggerCanBeEnabledByBuildPropertyAndDoesNotEatSpecialChars(RunnerInfo runnerInfo) @@ -54,8 +54,6 @@ public void MSBuildLoggerCanBeEnabledByBuildPropertyAndDoesNotEatSpecialChars(Ru } [TestMethod] - // patched dotnet is not published on non-windows systems - [TestCategory("Windows-Review")] [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] public void MSBuildLoggerCanBeDisabledByBuildProperty(RunnerInfo runnerInfo) { @@ -74,8 +72,6 @@ public void MSBuildLoggerCanBeDisabledByBuildProperty(RunnerInfo runnerInfo) [TestMethod] - // patched dotnet is not published on non-windows systems - [TestCategory("Windows-Review")] [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] public void MSBuildLoggerCanBeDisabledByEnvironmentVariableProperty(RunnerInfo runnerInfo) { diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ExecutionTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ExecutionTests.cs index 4fd4b85bb4..920a1c7f9b 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ExecutionTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ExecutionTests.cs @@ -16,8 +16,8 @@ namespace Microsoft.TestPlatform.AcceptanceTests; [TestClass] public class ExecutionTests : AcceptanceTestBase { - //TODO: It looks like the first 3 tests would be useful to multiply by all 3 test frameworks, should we make the test even more generic, or duplicate them? [TestMethod] + // Compatibility matrix includes the .NET Framework runner/testhost, which is not available on Linux/macOS. [TestCategory("Windows-Review")] [MSTestCompatibilityDataSource] public void RunMultipleTestAssemblies(RunnerInfo runnerInfo) @@ -35,6 +35,7 @@ public void RunMultipleTestAssemblies(RunnerInfo runnerInfo) } [TestMethod] + // Compatibility matrix includes the .NET Framework runner/testhost, which is not available on Linux/macOS. [TestCategory("Windows-Review")] [TestHostCompatibilityDataSource] public void RunMultipleMSTestAssembliesOnVstestConsoleAndTesthostCombinations(RunnerInfo runnerInfo) @@ -52,6 +53,7 @@ public void RunMultipleMSTestAssembliesOnVstestConsoleAndTesthostCombinations(Ru } [TestMethod] + // Compatibility matrix includes the .NET Framework runner/testhost, which is not available on Linux/macOS. [TestCategory("Windows-Review")] [RunnerCompatibilityDataSource] public void RunMultipleMSTestAssembliesOnVstestConsoleAndTesthostCombinations2(RunnerInfo runnerInfo) @@ -67,9 +69,7 @@ public void RunMultipleMSTestAssembliesOnVstestConsoleAndTesthostCombinations2(R } [TestMethod] - [TestCategory("Windows-Review")] [TestCategory("Smoke")] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true, useVsixRunner: true)] [NetCoreTargetFrameworkDataSource] public void RunMultipleMSTestAssembliesOnVstestConsoleAndTesthostCombinations3(RunnerInfo runnerInfo) { @@ -87,7 +87,6 @@ public void RunMultipleMSTestAssembliesOnVstestConsoleAndTesthostCombinations3(R // the two respective versions together (e.g. latest xunit and latest mstest), but does using two different test // frameworks have any added value over using 2 mstest dlls? [TestMethod] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] [NetCoreTargetFrameworkDataSource] public void RunMultipleTestAssembliesWithoutTestAdapterPath(RunnerInfo runnerInfo) { @@ -134,7 +133,6 @@ public void RunMultipleTestAssembliesInParallel(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] [NetCoreTargetFrameworkDataSource] public void TestSessionTimeOutTests(RunnerInfo runnerInfo) { @@ -156,7 +154,6 @@ public void TestSessionTimeOutTests(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] [NetCoreTargetFrameworkDataSource] public void WorkingDirectoryIsSourceDirectory(RunnerInfo runnerInfo) { @@ -173,6 +170,9 @@ public void WorkingDirectoryIsSourceDirectory(RunnerInfo runnerInfo) } [TestMethod] + // Asserts the testhost-specific stack overflow message; the .NET Framework variant requires the + // .NET Framework testhost, which is only available on Windows. + [TestCategory("Windows-Review")] [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void StackOverflowExceptionShouldBeLoggedToConsoleAndDiagLogFile(RunnerInfo runnerInfo) @@ -198,6 +198,9 @@ public void StackOverflowExceptionShouldBeLoggedToConsoleAndDiagLogFile(RunnerIn } [TestMethod] + // Asserts the testhost-specific unhandled exception message; the .NET Framework variant requires the + // .NET Framework testhost, which is only available on Windows. + [TestCategory("Windows-Review")] [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void UnhandleExceptionExceptionShouldBeLoggedToDiagLogFile(RunnerInfo runnerInfo) @@ -292,8 +295,7 @@ public void IncompatibleSourcesWarningShouldBeDisplayedInTheConsoleOnlyWhenRunni } [TestMethod] - [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] + [NetCoreTargetFrameworkDataSource] public void ExitCodeShouldReturnOneWhenTreatNoTestsAsErrorParameterSetToTrueAndNoTestMatchesFilter(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -312,8 +314,7 @@ public void ExitCodeShouldReturnOneWhenTreatNoTestsAsErrorParameterSetToTrueAndN } [TestMethod] - [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] + [NetCoreTargetFrameworkDataSource] public void ExitCodeShouldReturnZeroWhenTreatNoTestsAsErrorParameterSetToFalseAndNoTestMatchesFilter(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -366,8 +367,7 @@ public void ExitCodeShouldNotDependOnFailTreatNoTestsAsErrorFalseValueWhenThereA } [TestMethod] - [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] + [NetCoreTargetFrameworkDataSource] public void ExecuteTestsShouldSucceedWhenAtLeastOneDllFindsRuntimeProvider(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FilePatternParserTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FilePatternParserTests.cs index 7eddb74cc2..4365382eb3 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FilePatternParserTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FilePatternParserTests.cs @@ -12,7 +12,6 @@ namespace Microsoft.TestPlatform.AcceptanceTests; public class FilePatternParserTests : AcceptanceTestBase { [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void WildCardPatternShouldCorrectlyWorkOnFiles(RunnerInfo runnerInfo) { @@ -32,7 +31,6 @@ public void WildCardPatternShouldCorrectlyWorkOnFiles(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void WildCardPatternShouldCorrectlyWorkOnArbitraryDepthDirectories(RunnerInfo runnerInfo) { @@ -58,7 +56,6 @@ public void WildCardPatternShouldCorrectlyWorkOnArbitraryDepthDirectories(Runner } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void WildCardPatternShouldCorrectlyWorkForRelativeAssemblyPath(RunnerInfo runnerInfo) { @@ -84,7 +81,6 @@ public void WildCardPatternShouldCorrectlyWorkForRelativeAssemblyPath(RunnerInfo } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void WildCardPatternShouldCorrectlyWorkOnMultipleFiles(RunnerInfo runnerInfo) { diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FrameworkTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FrameworkTests.cs index c08561abe9..24a4da4add 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FrameworkTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FrameworkTests.cs @@ -13,7 +13,6 @@ public class FrameworkTests : AcceptanceTestBase { [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void FrameworkArgumentShouldWork(RunnerInfo runnerInfo) { @@ -27,7 +26,6 @@ public void FrameworkArgumentShouldWork(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void FrameworkShortNameArgumentShouldWork(RunnerInfo runnerInfo) { @@ -69,6 +67,7 @@ public void OnWrongFrameworkPassedTestRunShouldNotRun(RunnerInfo runnerInfo) [TestMethod] [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] + // The .NET (Core) runner produces a different framework-incompatible warning on non-Windows, so keep this Windows-only. [TestCategory("Windows-Review")] public void RunSpecificTestsShouldWorkWithFrameworkInCompatibleWarning(RunnerInfo runnerInfo) { diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/LoggerTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/LoggerTests.cs index 1922937371..86717def09 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/LoggerTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/LoggerTests.cs @@ -16,8 +16,7 @@ namespace Microsoft.TestPlatform.AcceptanceTests; public class LoggerTests : AcceptanceTestBase { [TestMethod] - [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] + [NetCoreTargetFrameworkDataSource] public void TrxLoggerWithFriendlyNameShouldProperlyOverwriteFile(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -37,8 +36,7 @@ public void TrxLoggerWithFriendlyNameShouldProperlyOverwriteFile(RunnerInfo runn } [TestMethod] - [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] + [NetCoreTargetFrameworkDataSource] public void HtmlLoggerWithFriendlyNameShouldProperlyOverwriteFile(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -58,8 +56,7 @@ public void HtmlLoggerWithFriendlyNameShouldProperlyOverwriteFile(RunnerInfo run } [TestMethod] - [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] + [NetCoreTargetFrameworkDataSource] public void HtmlLoggerWithFriendlyNameContainsExpectedContent(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -106,8 +103,7 @@ public void TrxLoggerWithExecutorUriShouldProperlyOverwriteFile(RunnerInfo runne } [TestMethod] - [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] + [NetCoreTargetFrameworkDataSource] public void TrxLoggerWithLogFilePrefixShouldGenerateMultipleTrx(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -147,8 +143,7 @@ public void HtmlLoggerWithExecutorUriShouldProperlyOverwriteFile(RunnerInfo runn } [TestMethod] - [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] + [NetCoreTargetFrameworkDataSource] public void TrxLoggerResultSummaryOutcomeValueShouldBeFailedIfNoTestsExecutedAndTreatNoTestsAsErrorIsTrue(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -170,8 +165,7 @@ public void TrxLoggerResultSummaryOutcomeValueShouldBeFailedIfNoTestsExecutedAnd } [TestMethod] - [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] + [NetCoreTargetFrameworkDataSource] public void TrxLoggerResultSummaryOutcomeValueShouldNotChangeIfNoTestsExecutedAndTreatNoTestsAsErrorIsFalse(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -193,7 +187,6 @@ public void TrxLoggerResultSummaryOutcomeValueShouldNotChangeIfNoTestsExecutedAn } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void TrxLoggerResultSummaryOutcomeValueShouldBeFailedWhenDataCollectorLogsError(RunnerInfo runnerInfo) { @@ -222,7 +215,6 @@ public void TrxLoggerResultSummaryOutcomeValueShouldBeFailedWhenDataCollectorLog } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void TrxLoggerResultSummaryOutcomeValueShouldBeCompletedWhenDataCollectorLogsErrorAndTreatErrorMessagesAsWarningsIsTrue(RunnerInfo runnerInfo) { @@ -315,7 +307,6 @@ private static void IsFileAndContentEqual(string filePath) } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void TrxLoggerShouldNotDoubleCountDataDrivenTestResults(RunnerInfo runnerInfo) { @@ -347,7 +338,6 @@ public void TrxLoggerShouldNotDoubleCountDataDrivenTestResults(RunnerInfo runner } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void TrxLoggerShouldPlaceTrxFileInSubdirectoryWhenLogFileNameContainsPath(RunnerInfo runnerInfo) { diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MultitargetingTestHostTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MultitargetingTestHostTests.cs index 4731d3586a..163238e783 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MultitargetingTestHostTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MultitargetingTestHostTests.cs @@ -12,6 +12,8 @@ namespace Microsoft.TestPlatform.AcceptanceTests; public class MultitargetingTestHostTests : AcceptanceTestBase { [TestMethod] + // Multitargeting is exercised against .NET Framework testhosts (net481), which only exist on Windows, + // so both data sources produce zero rows on Linux/macOS. [TestCategory("Windows-Review")] // the underlying test is using xUnit to avoid AppDomain enhancements in MSTest that make this pass even without multitargetting // xUnit supports net452 onwards, so that is why this starts at net452, I also don't test all framework versions diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/PortableNugetPackageTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/PortableNugetPackageTests.cs index 1e8006421e..1b42e0b8f0 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/PortableNugetPackageTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/PortableNugetPackageTests.cs @@ -24,7 +24,6 @@ public static void ClassInit(TestContext _) } [TestMethod] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] [NetCoreTargetFrameworkDataSource] public void RunMultipleTestAssemblies(RunnerInfo runnerInfo) { @@ -39,7 +38,6 @@ public void RunMultipleTestAssemblies(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] [NetCoreTargetFrameworkDataSource] public void DiscoverAllTests(RunnerInfo runnerInfo) { diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RecursiveResourcesLookupTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RecursiveResourcesLookupTests.cs index d5fcc7756b..6bdf038ef8 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RecursiveResourcesLookupTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RecursiveResourcesLookupTests.cs @@ -11,7 +11,8 @@ public class RecursiveResourcesLookupTests : AcceptanceTestBase { [TestMethod] // This only fails on .NET Framework, and it fails in testhost, so no need to double check with - // two different runners. + // two different runners. The NetFull data source is empty on Linux/macOS. + [TestCategory("Windows-Review")] [Ignore("Temporarily ignore until solving https://github.com/microsoft/testfx/issues/2692")] [NetFullTargetFrameworkDataSource(useCoreRunner: false)] public void RunsToCompletionWhenJapaneseResourcesAreLookedUpForMSCorLib(RunnerInfo runnerInfo) diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ResultsDirectoryTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ResultsDirectoryTests.cs index c59053faef..76e6937772 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ResultsDirectoryTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ResultsDirectoryTests.cs @@ -13,7 +13,6 @@ namespace Microsoft.TestPlatform.AcceptanceTests; public class ResultsDirectoryTests : AcceptanceTestBase { [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void TrxFileShouldBeCreatedInResultsDirectory(RunnerInfo runnerInfo) { @@ -34,7 +33,6 @@ public void TrxFileShouldBeCreatedInResultsDirectory(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void ResultsDirectoryRelativePathShouldWork(RunnerInfo runnerInfo) { diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RunsettingsTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RunsettingsTests.cs index 58ac39da92..2816b7e9b4 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RunsettingsTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RunsettingsTests.cs @@ -313,8 +313,6 @@ public void EnvironmentVariablesSettingsShouldSetEnvironmentVariables(RunnerInfo /// /// [TestMethod] - // patched dotnet is not published on non-windows systems - [TestCategory("Windows-Review")] [NetFullTargetFrameworkDataSourceAttribute(useDesktopRunner: false)] [NetCoreTargetFrameworkDataSourceAttribute(useDesktopRunner: false)] public void RunSettingsAreLoadedFromProject(RunnerInfo runnerInfo) diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SerializationCompatibilityTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SerializationCompatibilityTests.cs index 79b808c014..9cb64cea20 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SerializationCompatibilityTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SerializationCompatibilityTests.cs @@ -38,6 +38,7 @@ public class SerializationCompatibilityTests : AcceptanceTestBase /// Verifies that discovery request/response messages serialize correctly across the version boundary. /// [TestMethod] + // Compatibility matrix includes the .NET Framework runner/testhost, which is not available on Linux/macOS. [TestCategory("Windows-Review")] [RunnerCompatibilityDataSource()] public void DiscoverTests_LatestRunner_WithOlderTesthosts(RunnerInfo runnerInfo) @@ -73,6 +74,7 @@ public void DiscoverTests_LatestRunner_WithOlderTesthosts(RunnerInfo runnerInfo) /// Verifies that older runners can understand discovery responses from the new STJ-based testhost. /// [TestMethod] + // Compatibility matrix includes the .NET Framework runner/testhost, which is not available on Linux/macOS. [TestCategory("Windows-Review")] [TestHostCompatibilityDataSource] public void DiscoverTests_OlderRunners_WithLatestTesthost(RunnerInfo runnerInfo) @@ -109,6 +111,7 @@ public void DiscoverTests_OlderRunners_WithLatestTesthost(RunnerInfo runnerInfo) /// Verifies that test run messages (start, result, complete) serialize correctly across versions. /// [TestMethod] + // Compatibility matrix includes the .NET Framework runner/testhost, which is not available on Linux/macOS. [TestCategory("Windows-Review")] [RunnerCompatibilityDataSource] public void RunTests_LatestRunner_WithOlderTesthosts(RunnerInfo runnerInfo) @@ -148,6 +151,7 @@ public void RunTests_LatestRunner_WithOlderTesthosts(RunnerInfo runnerInfo) /// Verifies that older runners can process execution results from the new STJ-based testhost. /// [TestMethod] + // Compatibility matrix includes the .NET Framework runner/testhost, which is not available on Linux/macOS. [TestCategory("Windows-Review")] [TestHostCompatibilityDataSource] public void RunTests_OlderRunners_WithLatestTesthost(RunnerInfo runnerInfo) diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SerializerSelectionTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SerializerSelectionTests.cs index 9558c0f162..b21d517bd2 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SerializerSelectionTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SerializerSelectionTests.cs @@ -25,6 +25,9 @@ public void OnNetCoreRunner_ShouldUseSystemTextJson(RunnerInfo runnerInfo) } [TestMethod] + // The .NET Framework runner (and its Jsonite serializer) only runs on Windows; the core counterpart + // is covered by OnNetCoreRunner_ShouldUseSystemTextJson. + [TestCategory("Windows-Review")] [NetFrameworkRunner(Net481TargetFramework)] public void OnNetFrameworkRunner_ShouldUseJsonite(RunnerInfo runnerInfo) { diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TelemetryTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TelemetryTests.cs index ec7f1ad8c3..a972b9a62c 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TelemetryTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TelemetryTests.cs @@ -20,7 +20,6 @@ public class TelemetryTests : AcceptanceTestBase private const string LOG_TELEMETRY_PATH = "VSTEST_LOGTELEMETRY_PATH"; [TestMethod] - [NetFullTargetFrameworkDataSourceAttribute(inIsolation: true, inProcess: true)] [NetCoreTargetFrameworkDataSource] public void RunTestsShouldPublishMetrics(RunnerInfo runnerInfo) { @@ -30,7 +29,6 @@ public void RunTestsShouldPublishMetrics(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSourceAttribute(inIsolation: true, inProcess: true)] [NetCoreTargetFrameworkDataSource] public void DiscoverTestsShouldPublishMetrics(RunnerInfo runnerInfo) { diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TestCaseFilterTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TestCaseFilterTests.cs index 0afd627f46..32323d9a54 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TestCaseFilterTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TestCaseFilterTests.cs @@ -10,7 +10,6 @@ namespace Microsoft.TestPlatform.AcceptanceTests; public class TestCaseFilterTests : AcceptanceTestBase { [TestMethod] - [NetFullTargetFrameworkDataSourceAttribute(inIsolation: true, inProcess: true)] [NetCoreTargetFrameworkDataSource] public void RunSelectedTestsWithAndOperatorTrait(RunnerInfo runnerInfo) { @@ -27,7 +26,6 @@ public void RunSelectedTestsWithAndOperatorTrait(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void RunSelectedTestsWithCategoryTraitInMixCase(RunnerInfo runnerInfo) { @@ -44,7 +42,6 @@ public void RunSelectedTestsWithCategoryTraitInMixCase(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void RunSelectedTestsWithClassNameTrait(RunnerInfo runnerInfo) { @@ -61,7 +58,6 @@ public void RunSelectedTestsWithClassNameTrait(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void RunSelectedTestsWithFullyQualifiedNameTrait(RunnerInfo runnerInfo) { @@ -80,7 +76,6 @@ public void RunSelectedTestsWithFullyQualifiedNameTrait(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void RunSelectedTestsWithNameTrait(RunnerInfo runnerInfo) { @@ -97,7 +92,6 @@ public void RunSelectedTestsWithNameTrait(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void RunSelectedTestsWithOrOperatorTrait(RunnerInfo runnerInfo) { @@ -114,7 +108,6 @@ public void RunSelectedTestsWithOrOperatorTrait(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void RunSelectedTestsWithPriorityTrait(RunnerInfo runnerInfo) { @@ -135,7 +128,6 @@ public void RunSelectedTestsWithPriorityTrait(RunnerInfo runnerInfo) /// this command should provide same results as /TestCaseFilter:"FullyQualifiedName~UnitTest1". /// [TestMethod] - [NetFullTargetFrameworkDataSource] [NetCoreTargetFrameworkDataSource] public void TestCaseFilterShouldWorkIfOnlyPropertyValueGivenInExpression(RunnerInfo runnerInfo) { @@ -179,7 +171,6 @@ public void DiscoverMstestV1TestsWithAndOperatorTrait(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSourceAttribute(inIsolation: true, inProcess: true)] [NetCoreTargetFrameworkDataSource] public void RunSelectedTestsWithNoneTestCategoryFilterMatchesUncategorizedTests(RunnerInfo runnerInfo) { @@ -199,7 +190,6 @@ public void RunSelectedTestsWithNoneTestCategoryFilterMatchesUncategorizedTests( } [TestMethod] - [NetFullTargetFrameworkDataSourceAttribute(inIsolation: true, inProcess: true)] [NetCoreTargetFrameworkDataSource] public void RunSelectedTestsWithNoneTestCategoryNotEqualFilterMatchesCategorizedTests(RunnerInfo runnerInfo) { diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/.runsettings b/test/Microsoft.TestPlatform.Library.IntegrationTests/.runsettings index 823b5bb2d5..cd8b6eb4da 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/.runsettings +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/.runsettings @@ -1,5 +1,2 @@ - - true - \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/FilterSourceIntegrationTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/FilterSourceIntegrationTests.cs index a4d78efa44..f7a9c725ac 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/FilterSourceIntegrationTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/FilterSourceIntegrationTests.cs @@ -16,7 +16,6 @@ namespace Microsoft.TestPlatform.Library.IntegrationTests; public class FilterSourceIntegrationTests : AcceptanceTestBase { [TestMethod] - [NetFullTargetFrameworkDataSource(useDesktopRunner: false)] [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] public void FilterSourcePackage_AllTestsPass(RunnerInfo runnerInfo) { diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/CustomTestHostLauncherTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/CustomTestHostLauncherTests.cs index 55d68513a8..9a3f86259b 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/CustomTestHostLauncherTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/CustomTestHostLauncherTests.cs @@ -35,6 +35,7 @@ public void Cleanup() } [TestMethod] + // WrapperCompatibilityDataSource includes the .NET Framework runner, which is not available on Linux/macOS. [TestCategory("Windows-Review")] [WrapperCompatibilityDataSource()] public void RunTestsWithCustomTestHostLauncherAttachesToDebuggerUsingTheProvidedLauncher(RunnerInfo runnerInfo) @@ -57,6 +58,7 @@ public void RunTestsWithCustomTestHostLauncherAttachesToDebuggerUsingTheProvided } [TestMethod] + // WrapperCompatibilityDataSource includes the .NET Framework runner, which is not available on Linux/macOS. [TestCategory("Windows-Review")] [TestCategory("Feature")] [WrapperCompatibilityDataSource] diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DataCollectorAttachmentProcessor.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DataCollectorAttachmentProcessor.cs index 32fa1eb801..d85cb80977 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DataCollectorAttachmentProcessor.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DataCollectorAttachmentProcessor.cs @@ -22,6 +22,7 @@ namespace Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests; [TestClass] +// This test runs the packaged .NET Framework vstest.console.exe, which cannot start on Linux/macOS. [TestCategory("Windows-Review")] public class DataCollectorAttachmentProcessor : AcceptanceTestBase { diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DifferentTestFrameworkSimpleTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DifferentTestFrameworkSimpleTests.cs index 23ebdf9ca3..cd1c7ad13a 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DifferentTestFrameworkSimpleTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DifferentTestFrameworkSimpleTests.cs @@ -68,7 +68,8 @@ public void RunTestsWithNunitAdapter(RunnerInfo runnerInfo) } [TestMethod] - // there are logs in the diagnostic log, it is failing with NullReferenceException because path is null + // The xUnit adapter produces no results on Linux/macOS (diagnostic log shows a NullReferenceException because path is null), + // so the run returns an empty sequence and .First() throws. Keep this Windows-only. [TestCategory("Windows-Review")] [NetCoreTargetFrameworkDataSource] public void RunTestsWithXunitAdapter(RunnerInfo runnerInfo) @@ -104,8 +105,8 @@ public void RunTestsWithXunitAdapter(RunnerInfo runnerInfo) } [TestMethod] - [TestCategory("Windows-Review")] // TODO: this does not work with netcore testhost, why? + [TestCategory("Windows-Review")] [NetFullTargetFrameworkDataSource] public void RunTestsWithNonDllAdapter(RunnerInfo runnerInfo) { diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DiscoverTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DiscoverTests.cs index 5a96e743cd..190079f3a3 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DiscoverTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DiscoverTests.cs @@ -44,6 +44,7 @@ public void Cleanup() } [TestMethod] + // WrapperCompatibilityDataSource includes the .NET Framework runner, which is not available on Linux/macOS. [TestCategory("Windows-Review")] [WrapperCompatibilityDataSource] public void DiscoverTestsUsingDiscoveryEventHandler1(RunnerInfo runnerInfo) @@ -62,6 +63,7 @@ public void DiscoverTestsUsingDiscoveryEventHandler1(RunnerInfo runnerInfo) } [TestMethod] + // WrapperCompatibilityDataSource includes the .NET Framework runner, which is not available on Linux/macOS. [TestCategory("Windows-Review")] [WrapperCompatibilityDataSource] public void DiscoverTestsUsingDiscoveryEventHandler2AndTelemetryOptedOut(RunnerInfo runnerInfo) @@ -87,7 +89,6 @@ public void DiscoverTestsUsingDiscoveryEventHandler2AndTelemetryOptedOut(RunnerI [TestMethod] [TestCategory("Smoke")] [NetCoreTargetFrameworkDataSource] - [NetFullTargetFrameworkDataSource(useVsixRunner: true)] public void DiscoverTestsUsingDiscoveryEventHandler2AndTelemetryOptedIn(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -184,6 +185,8 @@ public void DiscoverTestUsingEventHandler2ShouldContainAllSourcesAsFullyDiscover // Normally we test on two runner, against single .NET Testhost, but because source navigation happens in testhost // it is better to test against both desktop and core runners to make sure source navigation discovery works in both scenarios. // We run .NET Runner -> .NET Testhost and .NET Framework Runner -> .NET Frameworks Testhost. + // The .NET Framework runner/testhost is not available on Linux/macOS. + [TestCategory("Windows-Review")] [NetFullTargetFrameworkDataSource(useCoreRunner: false)] [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] public void DiscoverTestsUsingSourceNavigation(RunnerInfo runnerInfo) diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/LiveUnitTestingTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/LiveUnitTestingTests.cs index 21afa15e53..38e5f2a347 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/LiveUnitTestingTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/LiveUnitTestingTests.cs @@ -37,6 +37,7 @@ public void Cleanup() [TestMethod] // Touches appdomain settings, preferring .NET Framework testhost here. + [TestCategory("Windows-Review")] [NetFullTargetFrameworkDataSource] public void DiscoverTestsUsingLiveUnitTesting(RunnerInfo runnerInfo) { @@ -62,6 +63,7 @@ public void DiscoverTestsUsingLiveUnitTesting(RunnerInfo runnerInfo) [TestMethod] // Touches appdomain settings, preferring .NET Framework testhost here. + [TestCategory("Windows-Review")] [NetFullTargetFrameworkDataSource] public void RunTestsWithLiveUnitTesting(RunnerInfo runnerInfo) { diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTests.cs index 5e394623b2..7975ecd58e 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTests.cs @@ -46,6 +46,7 @@ public void Cleanup() } [TestMethod] + // WrapperCompatibilityDataSource includes the .NET Framework runner, which is not available on Linux/macOS. [TestCategory("Windows-Review")] [WrapperCompatibilityDataSource] public void RunAllTests(RunnerInfo runnerInfo) @@ -65,7 +66,6 @@ public void RunAllTests(RunnerInfo runnerInfo) [TestMethod] [NetCoreTargetFrameworkDataSource] - [NetFullTargetFrameworkDataSource(useVsixRunner: true)] [TestCategory("Smoke")] public void RunAllTestsFromDlls(RunnerInfo runnerInfo) { @@ -83,6 +83,7 @@ public void RunAllTestsFromDlls(RunnerInfo runnerInfo) } [TestMethod] + // WrapperCompatibilityDataSource includes the .NET Framework runner, which is not available on Linux/macOS. [TestCategory("Windows-Review")] [WrapperCompatibilityDataSource()] public void RunAllTestsWithMixedTFMsWillRunTestsFromAllProvidedDllEvenWhenTheyMixTFMs(RunnerInfo runnerInfo) @@ -171,6 +172,9 @@ public void RunTestsWithTelemetryOptedOut(RunnerInfo runnerInfo) [TestMethod] // This is testing the behavior of crash in testhost, run on different testhost, and just .NET runner. + // The assertion below branches on the .NET Framework-specific stack overflow message, and the + // .NET Framework testhost is only available on Windows, so this runs as Windows-Review. + [TestCategory("Windows-Review")] [NetFullTargetFrameworkDataSource(useDesktopRunner: false)] [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] public void RunTestsShouldThrowOnStackOverflowException(RunnerInfo runnerInfo) @@ -195,8 +199,6 @@ public void RunTestsShouldThrowOnStackOverflowException(RunnerInfo runnerInfo) } [TestMethod] - [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource(useDesktopRunner: false)] [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] public void RunTestsShouldShowProperWarningOnNoTestsForTestCaseFilter(RunnerInfo runnerInfo) { diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTestsWithFilterTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTestsWithFilterTests.cs index decb300853..3dc564da17 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTestsWithFilterTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTestsWithFilterTests.cs @@ -37,6 +37,7 @@ public void Cleanup() } [TestMethod] + // WrapperCompatibilityDataSource includes the .NET Framework runner, which is not available on Linux/macOS. [TestCategory("Windows-Review")] [WrapperCompatibilityDataSource] public void RunTestsWithTestCaseFilter(RunnerInfo runnerInfo) diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/SerializeTestRunTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/SerializeTestRunTests.cs index 1c470a41b6..87f27d6787 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/SerializeTestRunTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/SerializeTestRunTests.cs @@ -17,8 +17,6 @@ namespace Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests; [TestClass] // TODO: this comment seems inaccurate and would mean all our linux and macos tests are broken? -// We need to dogfood the package built in this repo *-dev and we pack tha tp only on windows -[TestCategory("Windows-Review")] public class SerialTestRunDecoratorTests : AcceptanceTestBase { private IVsTestConsoleWrapper? _vstestConsoleWrapper; @@ -51,7 +49,6 @@ public void Cleanup() [TestMethod] // This is testhost concept, try it on combination of testhosts, and .NET Runner. [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] - [NetFullTargetFrameworkDataSource(useDesktopRunner: false)] public void DiscoverTestsAndRunTestsSequentially(RunnerInfo runnerInfo) { // Arrange @@ -73,7 +70,6 @@ public void DiscoverTestsAndRunTestsSequentially(RunnerInfo runnerInfo) [TestMethod] // This is testhost concept, try it on combination of testhosts, and .NET Runner. [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] - [NetFullTargetFrameworkDataSource(useDesktopRunner: false)] public void DiscoverTestsAndRunTestsSequentially_DisabledByFeatureFlag(RunnerInfo runnerInfo) { // Arrange @@ -95,7 +91,6 @@ public void DiscoverTestsAndRunTestsSequentially_DisabledByFeatureFlag(RunnerInf [TestMethod] [NetCoreTargetFrameworkDataSource] - [NetFullTargetFrameworkDataSource] public void DiscoverTestsAndRunTestsSequentially_IsNotSupportedForSources(RunnerInfo runnerInfo) { // Arrange diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/TargetFrameworkTestHostDemultiplexer.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/TargetFrameworkTestHostDemultiplexer.cs index bb553f04ca..c3b2d7d1b9 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/TargetFrameworkTestHostDemultiplexer.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/TargetFrameworkTestHostDemultiplexer.cs @@ -16,8 +16,6 @@ namespace Microsoft.TestPlatform.Library.IntegrationTests.TranslationLayerTests; [TestClass] -// We need to dogfood the package built in this repo *-dev and we pack tha tp only on windows -[TestCategory("Windows-Review")] public class TargetFrameworkTestHostDemultiplexer : AcceptanceTestBase { private IVsTestConsoleWrapper? _vstestConsoleWrapper; @@ -40,19 +38,16 @@ public void Cleanup() [TestMethod] [NetCoreTargetFrameworkDataSource] - [NetFullTargetFrameworkDataSource] public void ExecuteContainerInMultiHost(RunnerInfo runnerInfo) => ExecuteContainerInMultiHost(runnerInfo, 3); [TestMethod] [NetCoreTargetFrameworkDataSource] - [NetFullTargetFrameworkDataSource] public void ExecuteContainerInMultiHost_MoreHostsThanTests(RunnerInfo runnerInfo) => ExecuteContainerInMultiHost(runnerInfo, 20); [TestMethod] [NetCoreTargetFrameworkDataSource] - [NetFullTargetFrameworkDataSource] public void ExecuteSingleContainerInDefaultSingleHost(RunnerInfo runnerInfo) => ExecuteContainerInMultiHost(runnerInfo, -1); diff --git a/test/vstest.console.UnitTests/Processors/AeDebuggerArgumentProcessorTest.cs b/test/vstest.console.UnitTests/Processors/AeDebuggerArgumentProcessorTest.cs index c8b1645c71..bac053c84a 100644 --- a/test/vstest.console.UnitTests/Processors/AeDebuggerArgumentProcessorTest.cs +++ b/test/vstest.console.UnitTests/Processors/AeDebuggerArgumentProcessorTest.cs @@ -18,6 +18,7 @@ namespace vstest.console.UnitTests.Processors; [TestClass] +// AeDebug (post-mortem debugger) is a Windows-only feature and these tests rely on Windows path semantics. [TestCategory("Windows-Review")] public class AeDebuggerArgumentProcessorTest { diff --git a/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs index bed998f47f..4e9ebdb118 100644 --- a/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs @@ -393,7 +393,6 @@ public void InitializeShouldCreateEntryForBlameAlongWithCollectHangDumpParameter } [TestMethod] - [TestCategory("Windows-Review")] public void InitializeMonitorPostmortemDebuggerShouldGenerateCorrectConfiguration() { var runsettingsString = string.Format(CultureInfo.CurrentCulture, _defaultRunSettings, ""); @@ -434,7 +433,6 @@ public void InitializeMonitorPostmortemDebuggerShouldGenerateCorrectConfiguratio } [TestMethod] - [TestCategory("Windows-Review")] public void InitializeMonitorPostmortemDebuggerShouldGenerateCorrectConfigurationAlsoIfIncomplete() { var runsettingsString = string.Format(CultureInfo.CurrentCulture, _defaultRunSettings, ""); diff --git a/test/vstest.console.UnitTests/Processors/ShowDeprecateDotnetVStestMessageArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ShowDeprecateDotnetVStestMessageArgumentProcessorTests.cs index 59537431ab..ee748fa37c 100644 --- a/test/vstest.console.UnitTests/Processors/ShowDeprecateDotnetVStestMessageArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ShowDeprecateDotnetVStestMessageArgumentProcessorTests.cs @@ -7,7 +7,6 @@ namespace vstest.console.UnitTests.Processors; [TestClass] -[TestCategory("Windows-Review")] public class ShowDeprecateDotnetVStestMessageArgumentProcessorTests { [TestMethod] From 17971a0e627b9a885db41d493e534dc9143daac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Mon, 29 Jun 2026 15:21:25 +0200 Subject: [PATCH 03/87] Drop Mono fallback, run .NET Framework tests on Windows only (#16158) * Drop Mono fallback, run .NET Framework tests on Windows only The .NET Framework test host (testhost.exe) only runs on Windows. On Linux and macOS vstest launched it through Mono, but Mono is no longer supported and the .NET SDK on Linux no longer ships the TestHostNetFramework folder, so dotnet test on a net462 project failed with an opaque Mono error: Cannot open assembly '.../TestHostNetFramework/testhost.exe': No such file or directory. DefaultTestHostManager now throws a clear TestPlatformException when asked to run on a non-Windows OS instead of falling back to Mono. .NET tests are unaffected, they go through DotnetTestHostManager. Breaking change: running .NET Framework tests on Linux or macOS is no longer supported. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review: stricter assertions, drop no-op string.Format - Throw the resource string directly instead of string.Format with no args (avoids a latent FormatException if a translation ever adds braces). - Unit test asserts the full "supported on Windows only" sentence, not just "Windows". - Acceptance test asserts ExitCodeEquals(1) on non-Windows to prove the run fails fast, not just logs a warning. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Hosting/DefaultTestHostManager.cs | 37 ++++++-------- .../Resources/Resources.Designer.cs | 9 ++++ .../Resources/Resources.resx | 5 ++ .../Resources/xlf/Resources.cs.xlf | 9 ++++ .../Resources/xlf/Resources.de.xlf | 9 ++++ .../Resources/xlf/Resources.es.xlf | 9 ++++ .../Resources/xlf/Resources.fr.xlf | 9 ++++ .../Resources/xlf/Resources.it.xlf | 9 ++++ .../Resources/xlf/Resources.ja.xlf | 9 ++++ .../Resources/xlf/Resources.ko.xlf | 9 ++++ .../Resources/xlf/Resources.pl.xlf | 9 ++++ .../Resources/xlf/Resources.pt-BR.xlf | 9 ++++ .../Resources/xlf/Resources.ru.xlf | 9 ++++ .../Resources/xlf/Resources.tr.xlf | 9 ++++ .../Resources/xlf/Resources.zh-Hans.xlf | 9 ++++ .../Resources/xlf/Resources.zh-Hant.xlf | 9 ++++ .../FrameworkTests.cs | 38 +++++++++++++-- .../Hosting/DefaultTestHostManagerTests.cs | 48 ++++++------------- 18 files changed, 196 insertions(+), 58 deletions(-) diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DefaultTestHostManager.cs b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DefaultTestHostManager.cs index 87a84e36d7..0452489908 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DefaultTestHostManager.cs +++ b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DefaultTestHostManager.cs @@ -56,7 +56,6 @@ public class DefaultTestHostManager : ITestRuntimeProvider2 private readonly IProcessHelper _processHelper; private readonly IFileHelper _fileHelper; private readonly IEnvironment _environment; - private readonly IDotnetHostHelper _dotnetHostHelper; private readonly IEnvironmentVariableHelper _environmentVariableHelper; private bool _disableAppDomain; private Architecture _architecture; @@ -78,7 +77,6 @@ public DefaultTestHostManager() : this( new ProcessHelper(), new FileHelper(), - new DotnetHostHelper(), new PlatformEnvironment(), new EnvironmentVariableHelper()) { @@ -91,17 +89,14 @@ public DefaultTestHostManager() /// File helper instance. /// Instance of platform environment. /// The environment helper. - /// Instance of dotnet host helper. internal DefaultTestHostManager( IProcessHelper processHelper, IFileHelper fileHelper, - IDotnetHostHelper dotnetHostHelper, IEnvironment environment, IEnvironmentVariableHelper environmentVariableHelper) { _processHelper = processHelper; _fileHelper = fileHelper; - _dotnetHostHelper = dotnetHostHelper; _environment = environment; _environmentVariableHelper = environmentVariableHelper; } @@ -210,28 +205,26 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo( EqtTrace.Verbose("DefaultTestHostmanager.GetTestHostProcessStartInfo: Trying to use {0} from {1}", originalTestHostProcessName, testhostProcessPath); + // .NET Framework tests run through testhost.exe, which can only run on Windows. + // Running them on other operating systems previously relied on Mono, which is no + // longer supported. Fail with a clear message instead of launching Mono. + if (!_environment.OperatingSystem.Equals(PlatformOperatingSystem.Windows)) + { + throw new TestPlatformException(Resources.NetFrameworkTestsNotSupportedOnNonWindows); + } + var launcherPath = testhostProcessPath; var processName = _processHelper.GetCurrentProcessFileName(); if (processName is not null) { - if (!_environment.OperatingSystem.Equals(PlatformOperatingSystem.Windows) - && !processName.EndsWith(DotnetHostHelper.MONOEXENAME, StringComparison.OrdinalIgnoreCase)) - { - launcherPath = _dotnetHostHelper.GetMonoPath(); - argumentsString = testhostProcessPath.AddDoubleQuote() + " " + argumentsString; - } - else + // Patching the relative path for IDE scenarios. + if (!(processName.EndsWith("dotnet", StringComparison.OrdinalIgnoreCase) + || processName.EndsWith("dotnet.exe", StringComparison.OrdinalIgnoreCase)) + && !File.Exists(testhostProcessPath)) { - // Patching the relative path for IDE scenarios. - if (_environment.OperatingSystem.Equals(PlatformOperatingSystem.Windows) - && !(processName.EndsWith("dotnet", StringComparison.OrdinalIgnoreCase) - || processName.EndsWith("dotnet.exe", StringComparison.OrdinalIgnoreCase)) - && !File.Exists(testhostProcessPath)) - { - testhostProcessPath = Path.Combine(currentWorkingDirectory, "..", originalTestHostProcessName); - EqtTrace.Verbose("DefaultTestHostmanager.GetTestHostProcessStartInfo: Could not find {0} in previous location, now using {1}", originalTestHostProcessName, testhostProcessPath); - launcherPath = testhostProcessPath; - } + testhostProcessPath = Path.Combine(currentWorkingDirectory, "..", originalTestHostProcessName); + EqtTrace.Verbose("DefaultTestHostmanager.GetTestHostProcessStartInfo: Could not find {0} in previous location, now using {1}", originalTestHostProcessName, testhostProcessPath); + launcherPath = testhostProcessPath; } } diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/Resources.Designer.cs b/src/Microsoft.TestPlatform.TestHostProvider/Resources/Resources.Designer.cs index 4b3c612666..98248f73dd 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/Resources.Designer.cs +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/Resources.Designer.cs @@ -111,5 +111,14 @@ internal static string NoDotnetMuxerFoundForArchitecture } } + /// + /// Looks up a localized string similar to Running .NET Framework tests is supported on Windows only.. + /// + internal static string NetFrameworkTestsNotSupportedOnNonWindows { + get { + return ResourceManager.GetString("NetFrameworkTestsNotSupportedOnNonWindows", resourceCulture); + } + } + } } diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/Resources.resx b/src/Microsoft.TestPlatform.TestHostProvider/Resources/Resources.resx index a5850f83c6..9aafbc4eda 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/Resources.resx +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/Resources.resx @@ -147,4 +147,9 @@ The specified framework can be found at: '{0}' is the placeholder for 'dotnet.exe' or 'dotnet' value and depends on platform Windows/Unix, '{1}' is the placeholder for the architeture name like ARM64, X64 etc... + + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + \ No newline at end of file diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.cs.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.cs.xlf index 1246ba96df..8fe92b0e8a 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.cs.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.cs.xlf @@ -26,6 +26,15 @@ Ověřte, že: {0} + + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + + Unable to find {0}. Make sure test project has a nuget reference of package "Microsoft.NET.Test.Sdk". Nejde najít {0}. Ujistěte se, že testovací projekt má odkaz na balíček nuget Microsoft.NET.Test.Sdk. diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.de.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.de.xlf index 8333c34568..a4a148e621 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.de.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.de.xlf @@ -26,6 +26,15 @@ Bestätigen Sie, dass: {0} + + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + + Unable to find {0}. Make sure test project has a nuget reference of package "Microsoft.NET.Test.Sdk". "{0}" wurde nicht gefunden. Stellen Sie sicher, dass das Testprojekt einen NuGet-Verweis des Pakets "Microsoft.NET.Test.Sdk" aufweist. diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.es.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.es.xlf index 44c79eb54f..9f44a85e41 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.es.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.es.xlf @@ -26,6 +26,15 @@ Compruebe que: {0} + + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + + Unable to find {0}. Make sure test project has a nuget reference of package "Microsoft.NET.Test.Sdk". No se encuentra {0}. Asegúrese de que el proyecto de prueba tenga una referencia NuGet del paquete "Microsoft.NET.Test.Sdk". diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.fr.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.fr.xlf index 01c65b16c6..8027a007c6 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.fr.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.fr.xlf @@ -26,6 +26,15 @@ Vérifiez ce qui suit : {0} + + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + + Unable to find {0}. Make sure test project has a nuget reference of package "Microsoft.NET.Test.Sdk". {0} est introuvable. Vérifiez que le projet de test a une référence nuget du package "Microsoft.NET.Test.Sdk". diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.it.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.it.xlf index 2a27a568f0..cc456effc7 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.it.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.it.xlf @@ -26,6 +26,15 @@ Verificare che: {0} + + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + + Unable to find {0}. Make sure test project has a nuget reference of package "Microsoft.NET.Test.Sdk". Non è possibile trovare {0}. Assicurarsi che il progetto di test includa un riferimento NuGet del pacchetto "Microsoft.NET.Test.Sdk". diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ja.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ja.xlf index 09a00dd654..19326baa58 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ja.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ja.xlf @@ -26,6 +26,15 @@ Verify that: {0} + + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + + Unable to find {0}. Make sure test project has a nuget reference of package "Microsoft.NET.Test.Sdk". {0} を見つけることができません。テスト プロジェクトにパッケージ "Microsoft.NET.Test.Sdk" の NuGet 参照があることを確認してください。 diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ko.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ko.xlf index 88773a5784..5818e27d28 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ko.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ko.xlf @@ -26,6 +26,15 @@ Verify that: {0} + + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + + Unable to find {0}. Make sure test project has a nuget reference of package "Microsoft.NET.Test.Sdk". {0}을(를) 찾을 수 없습니다. 테스트 프로젝트에 "Microsoft.NET.Test.Sdk" 패키지의 nuget 참조가 포함되어 있는지 확인하세요. diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pl.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pl.xlf index 4959e232ca..0376620b20 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pl.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pl.xlf @@ -26,6 +26,15 @@ Sprawdź, czy: {0} + + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + + Unable to find {0}. Make sure test project has a nuget reference of package "Microsoft.NET.Test.Sdk". Nie można znaleźć elementu {0}. Upewnij się, że projekt testowy ma odwołanie nuget do pakietu „Microsoft.NET.Test.Sdk”. diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pt-BR.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pt-BR.xlf index 5a61e3e934..a2a8567eeb 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pt-BR.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pt-BR.xlf @@ -26,6 +26,15 @@ Verifique se: {0} + + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + + Unable to find {0}. Make sure test project has a nuget reference of package "Microsoft.NET.Test.Sdk". Não foi possível localizar {0}. Certifique-se de que o projeto de teste tem uma referência nuget do pacote "Microsoft.NET.Test.Sdk". diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ru.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ru.xlf index 4262a63f6f..eccc6af287 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ru.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ru.xlf @@ -26,6 +26,15 @@ Verify that: {0} + + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + + Unable to find {0}. Make sure test project has a nuget reference of package "Microsoft.NET.Test.Sdk". Не удается найти {0}. Убедитесь, что в тестовом проекте есть ссылка NuGet на пакет "Microsoft.NET.Test.Sdk". diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.tr.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.tr.xlf index 8372c3a1a0..daef6fcd2b 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.tr.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.tr.xlf @@ -26,6 +26,15 @@ Verify that: {0} + + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + + Unable to find {0}. Make sure test project has a nuget reference of package "Microsoft.NET.Test.Sdk". {0} bulunamıyor. Test projesinin "Microsoft.NET.Test.Sdk" paketinde nuget başvurusu olduğundan emin olun. diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hans.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hans.xlf index 6aced291ad..fde22fbdda 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hans.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hans.xlf @@ -26,6 +26,15 @@ Verify that: {0} + + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + + Unable to find {0}. Make sure test project has a nuget reference of package "Microsoft.NET.Test.Sdk". 无法找到 {0}。确保测试项目具有包 "Microsoft.NET.Test.Sdk" 的 nuget 引用。 diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hant.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hant.xlf index 9cdad4d9b6..d9bf964ed0 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hant.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hant.xlf @@ -27,6 +27,15 @@ Verify that: {0} + + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + Running .NET Framework tests is supported on Windows only. + +Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. + + Unable to find {0}. Make sure test project has a nuget reference of package "Microsoft.NET.Test.Sdk". 找不到 {0}。請確認測試專案有 "Microsoft.NET.Test.Sdk "套件的 nuget 參考。 diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FrameworkTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FrameworkTests.cs index 24a4da4add..12b9f7f676 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FrameworkTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FrameworkTests.cs @@ -86,10 +86,14 @@ public void RunSpecificTestsShouldWorkWithFrameworkInCompatibleWarning(RunnerInf // but the settings requested .NET Framework 4.0. The test will still run because .NET Framework is compatible, and in reality // the system has .NET Framework 481 or newer installed, which runs even if we ask for .NET Framework 4.0 testhost. // - // On Linux and Mac we execute only net11.0 tests, and even though we force .NET Framework, we end up running on mono - // which is suprisingly able to run the .NET CoreApp dll, so we still just see a warning and 1 completed test. + // This test is Windows-Review only, so it does not run on Linux or Mac in CI. If it is run there manually, + // forcing .NET Framework now fails fast, because the .NET Framework test host is no longer launched through Mono. var isWindows = Environment.OSVersion.Platform.ToString().StartsWith("Win"); - if (runnerInfo.TargetFramework.Contains("net11") && isWindows) + if (!isWindows) + { + StdErrorContains("Running .NET Framework tests is supported on Windows only"); + } + else if (runnerInfo.TargetFramework.Contains("net11")) { StdOutputContains("No test is available"); } @@ -99,4 +103,32 @@ public void RunSpecificTestsShouldWorkWithFrameworkInCompatibleWarning(RunnerInf ValidateSummaryStatus(1, 0, 0); } } + + [TestMethod] + [NetCoreTargetFrameworkDataSource] + public void RunningNetFrameworkTestsOnNonWindowsShouldFailWithClearError(RunnerInfo runnerInfo) + { + SetTestEnvironment(_testEnvironment, runnerInfo); + + // Force the run to use the .NET Framework test host (testhost.exe). That host exists only on + // Windows. On other operating systems we used to fall back to Mono, which is no longer supported, + // so the run should fail fast with a clear, actionable message instead of an opaque Mono error. + var arguments = PrepareArguments(GetSampleTestAssembly(), string.Empty, string.Empty, string.Empty, resultsDirectory: TempDirectory.Path); + arguments = string.Concat(arguments, " ", "/Framework:Framework40"); + + InvokeVsTest(arguments); + + var isWindows = Environment.OSVersion.Platform.ToString().StartsWith("Win"); + if (isWindows) + { + // On Windows the .NET Framework test host is available, so the "Windows only" error must not appear. + StdErrorDoesNotContains("Running .NET Framework tests is supported on Windows only"); + } + else + { + // The run must fail fast with a clear message, not merely log a warning. + StdErrorContains("Running .NET Framework tests is supported on Windows only"); + ExitCodeEquals(1); + } + } } diff --git a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DefaultTestHostManagerTests.cs b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DefaultTestHostManagerTests.cs index 68bf9ab331..b8d8102b9a 100644 --- a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DefaultTestHostManagerTests.cs +++ b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DefaultTestHostManagerTests.cs @@ -37,7 +37,6 @@ public class DefaultTestHostManagerTests private readonly Mock _mockMessageLogger; private readonly Mock _mockProcessHelper; private readonly Mock _mockFileHelper; - private readonly Mock _mockDotnetHostHelper; private readonly Mock _mockEnvironment; private readonly Mock _mockEnvironmentVariable; private readonly DefaultTestHostManager _testHostManager; @@ -54,13 +53,12 @@ public DefaultTestHostManagerTests() _mockProcessHelper = new Mock(); _mockFileHelper = new Mock(); _mockProcessHelper.Setup(ph => ph.GetCurrentProcessFileName()).Returns("vstest.console.exe"); - _mockDotnetHostHelper = new Mock(); _mockEnvironment = new Mock(); _mockEnvironmentVariable = new Mock(); _mockMessageLogger = new Mock(); - _testHostManager = new DefaultTestHostManager(_mockProcessHelper.Object, _mockFileHelper.Object, _mockDotnetHostHelper.Object, _mockEnvironment.Object, _mockEnvironmentVariable.Object); + _testHostManager = new DefaultTestHostManager(_mockProcessHelper.Object, _mockFileHelper.Object, _mockEnvironment.Object, _mockEnvironmentVariable.Object); _testHostManager.Initialize(_mockMessageLogger.Object, $" {Architecture.X64} {Framework.DefaultFramework} {false} "); _startInfo = _testHostManager.GetTestHostProcessStartInfo([], null, default); } @@ -177,38 +175,22 @@ public void GetTestHostProcessStartInfoShouldIncludeTestSourcePathInArgumentsIfN } [TestMethod] - public void GetTestHostProcessStartInfoShouldUseMonoAsHostOnNonWindowsIfNotStartedWithMono() + [DataRow(PlatformOperatingSystem.Unix, "/usr/bin/dotnet")] + [DataRow(PlatformOperatingSystem.Unix, "/usr/bin/mono")] + [DataRow(PlatformOperatingSystem.OSX, "/usr/local/share/dotnet/dotnet")] + [DataRow(PlatformOperatingSystem.OSX, "/usr/local/bin/mono")] + public void GetTestHostProcessStartInfoShouldThrowWhenRunningNetFrameworkTestsOnNonWindows(PlatformOperatingSystem operatingSystem, string currentProcessFileName) { - _mockProcessHelper.Setup(p => p.GetCurrentProcessFileName()).Returns("/usr/bin/dotnet"); - _mockEnvironment.Setup(e => e.OperatingSystem).Returns(PlatformOperatingSystem.Unix); - _mockDotnetHostHelper.Setup(d => d.GetMonoPath()).Returns("/usr/bin/mono"); - var source = @"C:\temp\a.dll"; - - var info = _testHostManager.GetTestHostProcessStartInfo( - new List() { source }, - null, - default); - - Assert.AreEqual("/usr/bin/mono", info.FileName); - Assert.Contains(Path.Combine("TestHostNetFramework", "testhost.exe"), info.Arguments!); - } + // .NET Framework tests can only run on Windows. On other operating systems we no longer + // fall back to Mono and instead fail with a clear, actionable message. + _mockProcessHelper.Setup(p => p.GetCurrentProcessFileName()).Returns(currentProcessFileName); + _mockEnvironment.Setup(e => e.OperatingSystem).Returns(operatingSystem); + var source = "/tmp/a.dll"; - [TestMethod] - public void GetTestHostProcessStartInfoShouldNotUseMonoAsHostOnNonWindowsIfStartedWithMono() - { - _mockProcessHelper.Setup(p => p.GetCurrentProcessFileName()).Returns("/usr/bin/mono"); - _mockEnvironment.Setup(e => e.OperatingSystem).Returns(PlatformOperatingSystem.Unix); - _mockDotnetHostHelper.Setup(d => d.GetMonoPath()).Returns("/usr/bin/mono"); - var source = @"C:\temp\a.dll"; - - var info = _testHostManager.GetTestHostProcessStartInfo( - new List() { source }, - null, - default); + var exception = Assert.ThrowsExactly( + () => _testHostManager.GetTestHostProcessStartInfo(new List() { source }, null, default)); - var testHostPath = Path.Combine("TestHostNetFramework", "testhost.exe"); - Assert.EndsWith(testHostPath, info.FileName); - Assert.DoesNotContain(testHostPath, info.Arguments!); + Assert.Contains("Running .NET Framework tests is supported on Windows only", exception.Message); } [TestMethod] @@ -656,7 +638,7 @@ public TestableTestHostManager( IProcessHelper processHelper, bool shared, IMessageLogger logger) - : base(processHelper, new FileHelper(), new DotnetHostHelper(), new PlatformEnvironment(), new EnvironmentVariableHelper()) + : base(processHelper, new FileHelper(), new PlatformEnvironment(), new EnvironmentVariableHelper()) { Initialize(logger, $" {architecture} {framework} {!shared} "); } From ae106970de07705fe5cf188870a497ada000f548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Mon, 29 Jun 2026 16:12:51 +0200 Subject: [PATCH 04/87] refactor: use switch expression for TargetInvocationException unwrap in BaseRunTests (#16181) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the conditional ternary with a switch expression, which is the idiomatic pattern in this codebase per project guidelines. No behavioral change — the switch expression is semantically equivalent to the ternary it replaces. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Execution/BaseRunTests.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/BaseRunTests.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/BaseRunTests.cs index 9e640745d7..552f082dad 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/BaseRunTests.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/BaseRunTests.cs @@ -223,9 +223,11 @@ public void RunTests() // instantiated via reflection and its constructor throws. Unwrap that wrapper to the // real exception so callers don't see the reflection noise. Any other exception is // preserved as-is so its concrete type and stack trace are not lost on the way out. - Exception realException = ex is TargetInvocationException tie && tie.InnerException is not null - ? tie.InnerException - : ex; + Exception realException = ex switch + { + TargetInvocationException { InnerException: { } inner } => inner, + _ => ex, + }; exception = new Exception(realException.Message, realException); isAborted = true; } From e72d45fafff31914d09f5ccc56c9fbbe4c7286c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Mon, 29 Jun 2026 16:39:32 +0200 Subject: [PATCH 05/87] [efficiency-improver] perf: short-circuit FilterExpression.Evaluate for single-condition leaf nodes (#16182) * perf: short-circuit FilterExpression.Evaluate for single-condition leaf nodes When a filter expression is a single-condition leaf (the common case for Contains/NotContains filters like FullyQualifiedName~Test), Evaluate previously always allocated two Stack objects and a lambda via IterateFilterExpression, even though the tree traversal does nothing useful for a single node. Add a fast path: if _condition is not null (i.e. this is a leaf node), call _condition.Evaluate directly, bypassing the stack traversal entirely. FastFilter does not handle Contains (~) operations, so every 'FullyQualifiedName~Test'-style filter falls into FilterExpression.Evaluate. For a single condition, the new path eliminates: - Stack allocation - Stack allocation - One lambda/closure allocation ~100 bytes GC pressure eliminated per test case evaluated with a single Contains/NotContains filter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Follow repo guidelines: 'is not null' instead of '!= null' Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Azat Mukhametshin Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../FilterExpression.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Microsoft.TestPlatform.Filter.Source/FilterExpression.cs b/src/Microsoft.TestPlatform.Filter.Source/FilterExpression.cs index a80780064f..507f127436 100644 --- a/src/Microsoft.TestPlatform.Filter.Source/FilterExpression.cs +++ b/src/Microsoft.TestPlatform.Filter.Source/FilterExpression.cs @@ -384,6 +384,14 @@ internal bool Evaluate(Func propertyValueProvider) ValidateArg.NotNull(propertyValueProvider, nameof(propertyValueProvider)); #endif + // Fast path: leaf node (single condition, no sub-expressions). + // Avoids allocating two Stack objects and a lambda for the common + // single-condition filter case (e.g. "FullyQualifiedName~Test"). + if (_condition is not null) + { + return _condition.Evaluate(propertyValueProvider); + } + return IterateFilterExpression((current, result) => { // Only the leaves have a condition value. From e683cdc0fa08b191c99cb8ab264295db837f2b6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Mon, 29 Jun 2026 17:32:01 +0200 Subject: [PATCH 06/87] perf: avoid string[1] allocation in Condition.Evaluate for single-string properties (#16179) When a test property value is a plain string (the common case for FullyQualifiedName, DisplayName, Source, etc.), Condition.Evaluate previously wrapped it in a new string[1] array before dispatching to EvaluateEqualOperation / EvaluateContainsOperation. This allocation happened on every test-case evaluation in the slow filter path (filters using '~', '!~', or mixed operators). Add a fast path that handles the string case inline, eliminating the transient string[1] per evaluated test case. The null and string[] cases are unchanged; non-string/non-array types retain the ToString() fallback for backward compatibility. Proxy metric: heap allocation count in the slow filter path. Expected reduction: ~1 string[1] (~24 bytes) per test case evaluated when a Contains/NotContains filter is active. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Condition.cs | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/Microsoft.TestPlatform.Filter.Source/Condition.cs b/src/Microsoft.TestPlatform.Filter.Source/Condition.cs index 12b0556273..ad5ddb37eb 100644 --- a/src/Microsoft.TestPlatform.Filter.Source/Condition.cs +++ b/src/Microsoft.TestPlatform.Filter.Source/Condition.cs @@ -157,8 +157,32 @@ internal bool Evaluate(Func propertyValueProvider) #if IS_VSTEST_REPO ValidateArg.NotNull(propertyValueProvider, nameof(propertyValueProvider)); #endif - var multiValue = GetPropertyValue(propertyValueProvider); - var result = Operation switch + var propertyValue = propertyValueProvider(Name); + + // Fast path: single string value (most common case for FullyQualifiedName, DisplayName, etc.) + // Avoids allocating a string[1] wrapper that the general multi-value path would create. + if (propertyValue is string singleValue) + { + return Operation switch + { + Operation.Equal => string.Equals(singleValue, Value, StringComparison.OrdinalIgnoreCase), + Operation.NotEqual => !string.Equals(singleValue, Value, StringComparison.OrdinalIgnoreCase), + Operation.Contains => singleValue.IndexOf(Value, StringComparison.OrdinalIgnoreCase) != -1, + Operation.NotContains => singleValue.IndexOf(Value, StringComparison.OrdinalIgnoreCase) == -1, + _ => false, + }; + } + + // Null, string[], or other types: use multi-value evaluation. + // Other types are coerced via ToString() for backward compatibility. + string[]? multiValue = propertyValue switch + { + null => null, + string[] arr => arr, + _ => new[] { propertyValue.ToString()! }, + }; + + return Operation switch { // if any value in multi-valued property matches 'this.Value', for Equal to evaluate true. Operation.Equal => EvaluateEqualOperation(multiValue), @@ -170,8 +194,6 @@ internal bool Evaluate(Func propertyValueProvider) Operation.NotContains => !EvaluateContainsOperation(multiValue), _ => false, }; - - return result; } /// @@ -292,25 +314,6 @@ private static Operation GetOperator(string operationString) }; } - /// - /// Returns property value for Property using propertValueProvider. - /// - private string[]? GetPropertyValue(Func propertyValueProvider) - { - var propertyValue = propertyValueProvider(Name); - if (null != propertyValue) - { - if (propertyValue is not string[] multiValue) - { - multiValue = new string[1]; - multiValue[0] = propertyValue.ToString()!; - } - return multiValue; - } - - return null; - } - internal static IEnumerable TokenizeFilterConditionString(string str) { return str == null ? throw new ArgumentNullException(nameof(str)) : TokenizeFilterConditionStringWorker(str); From a5719e6b7c20299591ce33d5f86452748a849d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Tue, 30 Jun 2026 14:30:05 +0200 Subject: [PATCH 07/87] Unify acceptance test data sources into [TestMatrix] and [CompatibilityMatrix] (#16174) * Unify acceptance test data sources into [TestMatrix] and [CompatibilityMatrix] The acceptance tests had nine data-source attributes that overlapped and read inconsistently - Runner vs VSTestConsole, exclusion bools you had to mentally invert. Collapse them into two that read positively: [TestMatrix(console, testHost, ...)] for the framework matrix and [CompatibilityMatrix(scenario)] for the version-compat matrix. You pin an axis instead of excluding one. Migrated all in-repo call sites (48 files, a literal name swap). The old attributes stay for now so the open PRs that still use them keep building; they come out in a follow-up once those have merged. Checked the new attributes against the old ones for every call shape - same frameworks, /InIsolation, VSIX rows and order all match. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Re-run CI (flaky RunTestsShouldThrowOnStackOverflowException, see #16128) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Make vsix additive in [TestMatrix] so it always adds a VSIX run Previously the VSIX row was nested under the NetFx console/host axes, so combinations like console: Net or testHost: Net silently dropped it. Emit it whenever vsix: true (Windows-only), independent of the axes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Unify TestMatrix console/testHost axes into a single Target enum VSTestConsole and TestHost were structurally identical ({ Both, NetFx, Net }). Collapse them into one Target enum and global-using-static it in the two integration test projects, so call sites read [TestMatrix(console: NetFx, testHost: Net)] The console:/testHost: parameter names carry the axis the type used to. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ArgumentProcessorTests.cs | 4 +- .../BlameDataCollectorTests.cs | 28 +-- .../CodeCoverageTests.cs | 32 ++-- .../CreateNoNewWindowTests.cs | 6 +- .../DataCollectionTests.cs | 10 +- .../DataCollectorTests.Coverlet.cs | 2 +- .../DebugAssertTests.cs | 2 +- .../DifferentTestFrameworkSimpleTests.cs | 16 +- .../DisableAppdomainTests.cs | 4 +- .../DiscoveryTests.cs | 10 +- .../DotnetTestMSBuildOutputTests.cs | 6 +- .../DotnetTestTests.cs | 12 +- .../EventLogCollectorTests.cs | 4 +- .../ExecutionTests.cs | 48 ++--- .../ExecutionThreadApartmentStateTests.cs | 8 +- .../FilePatternParserTests.cs | 8 +- .../FrameworkTests.cs | 12 +- .../GlobalUsings.cs | 7 + .../ListExtensionsTests.cs | 8 +- .../LoggerTests.cs | 24 +-- .../MissingTestSdkTests.cs | 4 +- .../MultitargetingTestHostTests.cs | 4 +- .../PlatformTests.cs | 8 +- .../PortableNugetPackageTests.cs | 4 +- .../ProcessesInteractionTests.cs | 2 +- .../RecursiveResourcesLookupTests.cs | 2 +- .../ResultsDirectoryTests.cs | 4 +- .../RunsettingsTests.cs | 44 ++--- .../SelfContainedAppTests.cs | 2 +- .../SerializationCompatibilityTests.cs | 8 +- .../SerializerSelectionTests.cs | 4 +- .../TelemetryTests.cs | 4 +- .../TestCaseFilterTests.cs | 22 +-- .../TestPlatformNugetPackageTests.cs | 4 +- .../VideoRecorderTests.cs | 2 +- .../AppDomainTests.cs | 2 +- .../FilterSourceIntegrationTests.cs | 2 +- .../GlobalUsings.cs | 7 + .../CodeCoverageTests.cs | 20 +-- .../CustomTestHostLauncherTests.cs | 4 +- .../DataCollectorAttachmentProcessor.cs | 2 +- .../DifferentTestFrameworkSimpleTests.cs | 6 +- .../TranslationLayerTests/DiscoverTests.cs | 18 +- .../LiveUnitTestingTests.cs | 4 +- .../TranslationLayerTests/RunSelectedTests.cs | 4 +- .../TranslationLayerTests/RunTests.cs | 18 +- ...RunTestsWithDifferentConfigurationTests.cs | 6 +- .../RunTestsWithFilterTests.cs | 4 +- .../SerializeTestRunTests.cs | 6 +- .../TargetFrameworkTestHostDemultiplexer.cs | 6 +- .../CompatibilityMatrixAttribute.cs | 119 ++++++++++++ .../TestMatrixAttribute.cs | 170 ++++++++++++++++++ 52 files changed, 535 insertions(+), 232 deletions(-) create mode 100644 test/Microsoft.TestPlatform.Acceptance.IntegrationTests/GlobalUsings.cs create mode 100644 test/Microsoft.TestPlatform.Library.IntegrationTests/GlobalUsings.cs create mode 100644 test/Microsoft.TestPlatform.TestUtilities/CompatibilityMatrixAttribute.cs create mode 100644 test/Microsoft.TestPlatform.TestUtilities/TestMatrixAttribute.cs diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ArgumentProcessorTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ArgumentProcessorTests.cs index 8f2fe25d03..1fe030eab1 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ArgumentProcessorTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ArgumentProcessorTests.cs @@ -11,7 +11,7 @@ public class ArgumentProcessorTests : AcceptanceTestBase { [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void PassingNoArgumentsToVsTestConsoleShouldPrintHelpMessage(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -32,7 +32,7 @@ public void PassingNoArgumentsToVsTestConsoleShouldPrintHelpMessage(RunnerInfo r } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void PassingInvalidArgumentsToVsTestConsoleShouldNotPrintHelpMessage(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/BlameDataCollectorTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/BlameDataCollectorTests.cs index b8f9f08e99..eafbcef5b2 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/BlameDataCollectorTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/BlameDataCollectorTests.cs @@ -40,7 +40,7 @@ public BlameDataCollectorTests() [TestMethod] [TestCategory("Windows-Review")] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void BlameDataCollectorShouldGiveCorrectTestCaseName(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -55,8 +55,8 @@ public void BlameDataCollectorShouldGiveCorrectTestCaseName(RunnerInfo runnerInf [TestMethod] [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource(useCoreRunner: false)] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: NetFx, testHost: NetFx)] + [TestMatrix(console: Net, testHost: Net)] public void BlameDataCollectorShouldOutputDumpFile(RunnerInfo runnerInfo) { @@ -79,7 +79,7 @@ public void BlameDataCollectorShouldOutputDumpFile(RunnerInfo runnerInfo) [TestMethod] [TestCategory("Windows-Review")] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: Net)] public void BlameDataCollectorShouldNotOutputDumpFileWhenNoCrashOccurs(RunnerInfo runnerInfo) { @@ -103,7 +103,7 @@ public void BlameDataCollectorShouldNotOutputDumpFileWhenNoCrashOccurs(RunnerInf [TestMethod] [TestCategory("Windows-Review")] // This tests .net runner and .net framework runner, together with .net framework testhost. - [NetFullTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] public void BlameDataCollectorShouldOutputDumpFileWhenNoCrashOccursButCollectAlwaysIsEnabled(RunnerInfo runnerInfo) { @@ -125,7 +125,7 @@ public void BlameDataCollectorShouldOutputDumpFileWhenNoCrashOccursButCollectAlw } [TestMethod] - [NetCoreRunner("net481;net11.0")] + [TestMatrix(console: Net)] public void HangDumpOnTimeout(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -147,7 +147,7 @@ public void HangDumpOnTimeout(RunnerInfo runnerInfo) [TestMethod] // .NET testhost does not support dump on exit - [NetFullTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] public void CrashDumpWhenThereIsNoTimeout(RunnerInfo runnerInfo) { @@ -169,7 +169,7 @@ public void CrashDumpWhenThereIsNoTimeout(RunnerInfo runnerInfo) [TestMethod] // .NET tfms do not support dump on exit, but runner does - [NetFullTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] public void CrashDumpOnExit(RunnerInfo runnerInfo) { @@ -190,7 +190,7 @@ public void CrashDumpOnExit(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreRunner("net481;net11.0")] + [TestMatrix(console: Net)] public void CrashDumpOnStackOverflow(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -210,7 +210,7 @@ public void CrashDumpOnStackOverflow(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreRunner(NET)] + [TestMatrix(console: Net, testHost: Net)] public void CrashDumpChildProcesses(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -224,7 +224,7 @@ public void CrashDumpChildProcesses(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreRunner("net481;net11.0")] + [TestMatrix(console: Net)] public void HangDumpChildProcesses(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -240,7 +240,7 @@ public void HangDumpChildProcesses(RunnerInfo runnerInfo) [TestMethod] [DoNotParallelize] // Modifies the test asset's runtimeconfig.json on disk. - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void HangDumpShouldNotHangWhenTestHostFailsToStart(RunnerInfo runnerInfo) { // When testhost can't start (e.g. wrong runtime version), the inactivity timer @@ -281,8 +281,8 @@ public void HangDumpShouldNotHangWhenTestHostFailsToStart(RunnerInfo runnerInfo) [TestMethod] [TestCategory("Windows-Review")] [DoNotParallelize] // Installs/uninstalls procdump as machine-wide postmortem debugger via HKLM registry. - [NetFullTargetFrameworkDataSource] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] + [TestMatrix(testHost: Net)] public void BlameDataCollectorAeDebuggerShouldCollectDump(RunnerInfo runnerInfo) { // For convenience skip locally, but never skip in CI. If this cannot pass in CI we are not testing it at all. diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CodeCoverageTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CodeCoverageTests.cs index a7f4994e6e..39e11f0767 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CodeCoverageTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CodeCoverageTests.cs @@ -48,8 +48,8 @@ public enum SettingsType public class CodeCoverageTests : CodeCoverageAcceptanceTestBase { [TestMethod] - [NetFullTargetFrameworkDataSource(useDesktopRunner: false)] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: NetFx)] + [TestMatrix(console: Net, testHost: Net)] public void CollectCodeCoverageWithCollectOptionForx86(RunnerInfo runnerInfo) { var parameters = new TestParameters() @@ -67,8 +67,8 @@ public void CollectCodeCoverageWithCollectOptionForx86(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource(useDesktopRunner: false)] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: NetFx)] + [TestMatrix(console: Net, testHost: Net)] public void CollectCodeCoverageWithCollectOptionForx64(RunnerInfo runnerInfo) { var parameters = new TestParameters() @@ -86,8 +86,8 @@ public void CollectCodeCoverageWithCollectOptionForx64(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource(useDesktopRunner: false)] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: NetFx)] + [TestMatrix(console: Net, testHost: Net)] public void CollectCodeCoverageX86WithRunSettings(RunnerInfo runnerInfo) { var parameters = new TestParameters() @@ -105,8 +105,8 @@ public void CollectCodeCoverageX86WithRunSettings(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource(useDesktopRunner: false)] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: NetFx)] + [TestMatrix(console: Net, testHost: Net)] public void CollectCodeCoverageX64WithRunSettings(RunnerInfo runnerInfo) { var parameters = new TestParameters() @@ -124,8 +124,8 @@ public void CollectCodeCoverageX64WithRunSettings(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource(useDesktopRunner: false)] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: NetFx)] + [TestMatrix(console: Net, testHost: Net)] public void CodeCoverageShouldAvoidExclusionsX86(RunnerInfo runnerInfo) { var parameters = new TestParameters() @@ -146,8 +146,8 @@ public void CodeCoverageShouldAvoidExclusionsX86(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource(useDesktopRunner: false)] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: NetFx)] + [TestMatrix(console: Net, testHost: Net)] public void CodeCoverageShouldAvoidExclusionsX64(RunnerInfo runnerInfo) { var parameters = new TestParameters() @@ -168,8 +168,8 @@ public void CodeCoverageShouldAvoidExclusionsX64(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource(useDesktopRunner: false)] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: NetFx)] + [TestMatrix(console: Net, testHost: Net)] public void CollectCodeCoverageSpecifyOutputFormatXml(RunnerInfo runnerInfo) { var parameters = new TestParameters() @@ -187,8 +187,8 @@ public void CollectCodeCoverageSpecifyOutputFormatXml(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource(useDesktopRunner: false)] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: NetFx)] + [TestMatrix(console: Net, testHost: Net)] public void CollectCodeCoverageSpecifyOutputFormatCoberturaOverrideRunSettingsConfiguration(RunnerInfo runnerInfo) { var parameters = new TestParameters() diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CreateNoNewWindowTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CreateNoNewWindowTests.cs index 5122bec933..cfdeab3c83 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CreateNoNewWindowTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/CreateNoNewWindowTests.cs @@ -14,7 +14,7 @@ public class CreateNoNewWindowTests : AcceptanceTestBase [TestMethod] // CreateNoNewWindow maps to the Windows-only process CreateNoWindow flag and only runs on the .NET Framework testhost. [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: false)] + [TestMatrix(testHost: NetFx)] public void WhenCreateNoNewWindowIsFalse_DiagShowsCreateNoWindowFalse(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -38,7 +38,7 @@ public void WhenCreateNoNewWindowIsFalse_DiagShowsCreateNoWindowFalse(RunnerInfo [TestMethod] // CreateNoNewWindow maps to the Windows-only process CreateNoWindow flag and only runs on the .NET Framework testhost. [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: false)] + [TestMatrix(testHost: NetFx)] public void WhenCreateNoNewWindowIsTrue_DiagShowsCreateNoWindowTrue(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -62,7 +62,7 @@ public void WhenCreateNoNewWindowIsTrue_DiagShowsCreateNoWindowTrue(RunnerInfo r [TestMethod] // CreateNoNewWindow maps to the Windows-only process CreateNoWindow flag and only runs on the .NET Framework testhost. [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: false)] + [TestMatrix(testHost: NetFx)] public void WhenCreateNoNewWindowIsNotSet_DefaultIsTrue(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectionTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectionTests.cs index af0b35edcc..f9d8995ba3 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectionTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectionTests.cs @@ -20,7 +20,7 @@ namespace Microsoft.TestPlatform.AcceptanceTests; public class DataCollectionTests : AcceptanceTestBase { [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void ExecuteTestsWithDataCollection(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -43,7 +43,7 @@ public void ExecuteTestsWithDataCollection(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void ExecuteTestsWithDataCollectionUsingCollectArgument(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -66,7 +66,7 @@ public void ExecuteTestsWithDataCollectionUsingCollectArgument(RunnerInfo runner } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void DataCollectorAssemblyLoadingShouldNotThrowErrorForNetCore(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -80,7 +80,7 @@ public void DataCollectorAssemblyLoadingShouldNotThrowErrorForNetCore(RunnerInfo [TestMethod] // .NET Framework testhost-specific assembly loading; not applicable to the netcore testhost. [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] public void DataCollectorAssemblyLoadingShouldNotThrowErrorForFullFramework(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -92,7 +92,7 @@ public void DataCollectorAssemblyLoadingShouldNotThrowErrorForFullFramework(Runn } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void DataCollectorAttachmentProcessor(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectorTests.Coverlet.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectorTests.Coverlet.cs index 70ee8055db..848a6e55a7 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectorTests.Coverlet.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DataCollectorTests.Coverlet.cs @@ -14,7 +14,7 @@ namespace Microsoft.TestPlatform.AcceptanceTests; public class DataCollectorTestsCoverlets : AcceptanceTestBase { [TestMethod] - [NetCoreRunner(HOST_NET)] + [TestMatrix(console: Net, testHost: Net)] public void RunCoverletCoverage(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DebugAssertTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DebugAssertTests.cs index d6578bb9d8..23f59682f0 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DebugAssertTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DebugAssertTests.cs @@ -11,7 +11,7 @@ public class DebugAssertTests : AcceptanceTestBase { [TestMethod] // this is core only, there is nothing we can do about TPDebug.Assert crashing the process on framework - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: Net)] public void RunningTestWithAFailingDebugAssertDoesNotCrashTheHostingProcess(RunnerInfo runnerInfo) { // when debugging this test in case it starts failing, be aware that the default behavior of TPDebug.Assert diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DifferentTestFrameworkSimpleTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DifferentTestFrameworkSimpleTests.cs index db1d2a9e00..d1520a787a 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DifferentTestFrameworkSimpleTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DifferentTestFrameworkSimpleTests.cs @@ -15,7 +15,7 @@ namespace Microsoft.TestPlatform.AcceptanceTests; public class DifferentTestFrameworkSimpleTests : AcceptanceTestBase { [TestMethod] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] + [TestMatrix(testHost: NetFx, inProcess: true)] public void NonDllRunAllTestExecution(RunnerInfo runnerInfo) { // This used to test Chutzpah, to prove that we can run tests that are not shipped in dlls. @@ -36,7 +36,7 @@ public void NonDllRunAllTestExecution(RunnerInfo runnerInfo) [TestMethod] // vstest.console is x64 now, but x86 run "in process" run should still succeed by being run in x86 testhost // Skip .NET (Core) tests because we test them below. - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true, useCoreRunner: false)] + [TestMatrix(console: NetFx, testHost: NetFx, inProcess: true)] public void CPPRunAllTestExecutionNetFramework(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -48,7 +48,7 @@ public void CPPRunAllTestExecutionNetFramework(RunnerInfo runnerInfo) [TestCategory("Windows-Review")] // vstest.console is 64-bit now, run in process to test the 64-bit native dll // Skip .NET (Core) tests because we test them below. - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true, useCoreRunner: false)] + [TestMatrix(console: NetFx, testHost: NetFx, inProcess: true)] public void CPPRunAllTestExecutionPlatformx64NetFramework(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -60,7 +60,7 @@ public void CPPRunAllTestExecutionPlatformx64NetFramework(RunnerInfo runnerInfo) // C++ tests cannot run in .NET Framework host under .NET Core, because we only ship .NET Standard CPP adapter in .NET Core // We also don't test x86 for .NET Core, because the resolver there does not switch between x86 and x64 correctly, it just uses the parent process bitness. // We run this on netcore31 and not the default netcore21 because netcore31 is the minimum tfm that has the runtime features we need, such as additionaldeps. - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false, useCoreRunner: true)] + [TestMatrix(console: Net, testHost: Net)] public void CPPRunAllTestExecutionPlatformx64Net(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -68,8 +68,8 @@ public void CPPRunAllTestExecutionPlatformx64Net(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx, inProcess: true)] + [TestMatrix(testHost: Net)] public void NUnitRunAllTestExecution(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -84,8 +84,8 @@ public void NUnitRunAllTestExecution(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx, inProcess: true)] + [TestMatrix(testHost: Net)] public void XUnitRunAllTestExecution(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DisableAppdomainTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DisableAppdomainTests.cs index 1e18cf6e7f..15c0ca72ec 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DisableAppdomainTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DisableAppdomainTests.cs @@ -17,7 +17,7 @@ public class DisableAppdomainTests : AcceptanceTestBase [TestMethod] [TestCategory("Windows")] // Run in .NET Framework testhost, disabling appdomain will force running out of process in all cases. - [NetFullTargetFrameworkDataSource(inProcess: true)] + [TestMatrix(testHost: NetFx, inProcess: true)] public void DisableAppdomainTest(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -30,7 +30,7 @@ public void DisableAppdomainTest(RunnerInfo runnerInfo) [TestMethod] [TestCategory("Windows")] - [NetFullTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] public void NewtonSoftDependencyWithDisableAppdomainTest(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiscoveryTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiscoveryTests.cs index 23d047a76f..81f4687965 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiscoveryTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiscoveryTests.cs @@ -18,7 +18,7 @@ namespace Microsoft.TestPlatform.AcceptanceTests; public class DiscoveryTests : AcceptanceTestBase { [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void DiscoverAllTests(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -31,7 +31,7 @@ public void DiscoverAllTests(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] [TestCategory("Smoke")] public void MultipleSourcesDiscoverAllTests(RunnerInfo runnerInfo) { @@ -53,7 +53,7 @@ public void MultipleSourcesDiscoverAllTests(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void DiscoverFullyQualifiedTests(RunnerInfo runnerInfo) { var dummyFilePath = Path.Combine(TempDirectory.Path, $"{Guid.NewGuid()}.txt"); @@ -71,7 +71,7 @@ public void DiscoverFullyQualifiedTests(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void DiscoverTestsShouldShowProperWarningIfNoTestsOnTestCaseFilter(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -115,7 +115,7 @@ public void TypesToLoadAttributeTests() } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void DiscoverTestsShouldSucceedWhenAtLeastOneDllFindsRuntimeProvider(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestMSBuildOutputTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestMSBuildOutputTests.cs index 7602917081..103ed4eac4 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestMSBuildOutputTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestMSBuildOutputTests.cs @@ -18,7 +18,7 @@ public class DotnetTestMSBuildOutputTests : AcceptanceTestBase [TestMethod] // Special characters (~, !, |, %) don't survive the MSBuildLogger output round-trip on non-Windows terminals. [TestCategory("Windows-Review")] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: Net)] public void MSBuildLoggerCanBeEnabledByBuildPropertyAndDoesNotEatSpecialChars(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -54,7 +54,7 @@ public void MSBuildLoggerCanBeEnabledByBuildPropertyAndDoesNotEatSpecialChars(Ru } [TestMethod] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: Net)] public void MSBuildLoggerCanBeDisabledByBuildProperty(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -72,7 +72,7 @@ public void MSBuildLoggerCanBeDisabledByBuildProperty(RunnerInfo runnerInfo) [TestMethod] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: Net)] public void MSBuildLoggerCanBeDisabledByEnvironmentVariableProperty(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestTests.cs index d833cad647..005fa0e3c3 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DotnetTestTests.cs @@ -18,7 +18,7 @@ private static string GetFinalVersion(string version) } [TestMethod] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: Net)] [TestCategory("Smoke")] public void RunDotnetTestWithCsproj(RunnerInfo runnerInfo) { @@ -34,7 +34,7 @@ public void RunDotnetTestWithCsproj(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: Net)] public void RunDotnetTestWithDll(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -49,7 +49,7 @@ public void RunDotnetTestWithDll(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: Net)] public void RunDotnetTestWithCsprojPassInlineSettings(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -64,7 +64,7 @@ public void RunDotnetTestWithCsprojPassInlineSettings(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: Net)] public void RunDotnetTestWithDllPassInlineSettings(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -77,7 +77,7 @@ public void RunDotnetTestWithDllPassInlineSettings(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: Net)] [Ignore("TODO: This scenario is broken in real environment as well (running with shipped `dotnet test`. Old tests (before arcade) use location of vstest.console that have more dlls in place than what we ship, and they make it work.")] public void RunDotnetTestWithNativeDll(RunnerInfo runnerInfo) { @@ -93,7 +93,7 @@ public void RunDotnetTestWithNativeDll(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: Net)] public void RunDotnetTestAndSeeOutputFromConsoleWriteLine(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/EventLogCollectorTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/EventLogCollectorTests.cs index 4eeb03dcfb..8f0b10c4ca 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/EventLogCollectorTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/EventLogCollectorTests.cs @@ -20,7 +20,7 @@ public class EventLogCollectorTests : AcceptanceTestBase [Ignore] [TestMethod] [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] public void EventLogDataCollectorShoudCreateLogFileHavingEvents(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -41,7 +41,7 @@ public void EventLogDataCollectorShoudCreateLogFileHavingEvents(RunnerInfo runne [TestMethod] [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] public void EventLogDataCollectorShoudCreateLogFileWithoutEventsIfEventsAreNotLogged(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ExecutionTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ExecutionTests.cs index 920a1c7f9b..41b8daa30a 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ExecutionTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ExecutionTests.cs @@ -19,7 +19,7 @@ public class ExecutionTests : AcceptanceTestBase [TestMethod] // Compatibility matrix includes the .NET Framework runner/testhost, which is not available on Linux/macOS. [TestCategory("Windows-Review")] - [MSTestCompatibilityDataSource] + [CompatibilityMatrix(CompatScenario.Adapter)] public void RunMultipleTestAssemblies(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -37,7 +37,7 @@ public void RunMultipleTestAssemblies(RunnerInfo runnerInfo) [TestMethod] // Compatibility matrix includes the .NET Framework runner/testhost, which is not available on Linux/macOS. [TestCategory("Windows-Review")] - [TestHostCompatibilityDataSource] + [CompatibilityMatrix(CompatScenario.TestHost)] public void RunMultipleMSTestAssembliesOnVstestConsoleAndTesthostCombinations(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -55,7 +55,7 @@ public void RunMultipleMSTestAssembliesOnVstestConsoleAndTesthostCombinations(Ru [TestMethod] // Compatibility matrix includes the .NET Framework runner/testhost, which is not available on Linux/macOS. [TestCategory("Windows-Review")] - [RunnerCompatibilityDataSource] + [CompatibilityMatrix(CompatScenario.VSTestConsole)] public void RunMultipleMSTestAssembliesOnVstestConsoleAndTesthostCombinations2(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -70,7 +70,7 @@ public void RunMultipleMSTestAssembliesOnVstestConsoleAndTesthostCombinations2(R [TestMethod] [TestCategory("Smoke")] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void RunMultipleMSTestAssembliesOnVstestConsoleAndTesthostCombinations3(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -87,7 +87,7 @@ public void RunMultipleMSTestAssembliesOnVstestConsoleAndTesthostCombinations3(R // the two respective versions together (e.g. latest xunit and latest mstest), but does using two different test // frameworks have any added value over using 2 mstest dlls? [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void RunMultipleTestAssembliesWithoutTestAdapterPath(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -106,7 +106,7 @@ public void RunMultipleTestAssembliesWithoutTestAdapterPath(RunnerInfo runnerInf // and after --arch feature implementation we won't find correct muxer on CI. [TestCategory("Windows")] [TestMethod] - [MSTestCompatibilityDataSource] + [CompatibilityMatrix(CompatScenario.Adapter)] public void RunMultipleTestAssembliesInParallel(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -133,7 +133,7 @@ public void RunMultipleTestAssembliesInParallel(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void TestSessionTimeOutTests(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -154,7 +154,7 @@ public void TestSessionTimeOutTests(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void WorkingDirectoryIsSourceDirectory(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -173,8 +173,8 @@ public void WorkingDirectoryIsSourceDirectory(RunnerInfo runnerInfo) // Asserts the testhost-specific stack overflow message; the .NET Framework variant requires the // .NET Framework testhost, which is only available on Windows. [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] + [TestMatrix(testHost: Net)] public void StackOverflowExceptionShouldBeLoggedToConsoleAndDiagLogFile(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -201,8 +201,8 @@ public void StackOverflowExceptionShouldBeLoggedToConsoleAndDiagLogFile(RunnerIn // Asserts the testhost-specific unhandled exception message; the .NET Framework variant requires the // .NET Framework testhost, which is only available on Windows. [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] + [TestMatrix(testHost: Net)] public void UnhandleExceptionExceptionShouldBeLoggedToDiagLogFile(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -224,7 +224,7 @@ public void UnhandleExceptionExceptionShouldBeLoggedToDiagLogFile(RunnerInfo run [TestMethod] [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] public void IncompatibleSourcesWarningShouldBeDisplayedInTheConsoleWhenGivenIncompatibleX86andX64Dll(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -246,7 +246,7 @@ public void IncompatibleSourcesWarningShouldBeDisplayedInTheConsoleWhenGivenInco [TestMethod] [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] public void NoIncompatibleSourcesWarningShouldBeDisplayedInTheConsoleWhenGivenSingleX86Dll(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -266,7 +266,7 @@ public void NoIncompatibleSourcesWarningShouldBeDisplayedInTheConsoleWhenGivenSi [TestMethod] [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] public void IncompatibleSourcesWarningShouldBeDisplayedInTheConsoleOnlyWhenRunningIn32BitOS(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -295,7 +295,7 @@ public void IncompatibleSourcesWarningShouldBeDisplayedInTheConsoleOnlyWhenRunni } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void ExitCodeShouldReturnOneWhenTreatNoTestsAsErrorParameterSetToTrueAndNoTestMatchesFilter(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -314,7 +314,7 @@ public void ExitCodeShouldReturnOneWhenTreatNoTestsAsErrorParameterSetToTrueAndN } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void ExitCodeShouldReturnZeroWhenTreatNoTestsAsErrorParameterSetToFalseAndNoTestMatchesFilter(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -333,7 +333,7 @@ public void ExitCodeShouldReturnZeroWhenTreatNoTestsAsErrorParameterSetToFalseAn [TestMethod] [TestCategory("Windows")] - [NetFullTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] public void ExitCodeShouldNotDependOnTreatNoTestsAsErrorTrueValueWhenThereAreAnyTestsToRun(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -351,7 +351,7 @@ public void ExitCodeShouldNotDependOnTreatNoTestsAsErrorTrueValueWhenThereAreAny [TestMethod] [TestCategory("Windows")] - [NetFullTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] public void ExitCodeShouldNotDependOnFailTreatNoTestsAsErrorFalseValueWhenThereAreAnyTestsToRun(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -367,7 +367,7 @@ public void ExitCodeShouldNotDependOnFailTreatNoTestsAsErrorFalseValueWhenThereA } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void ExecuteTestsShouldSucceedWhenAtLeastOneDllFindsRuntimeProvider(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -389,7 +389,7 @@ public void ExecuteTestsShouldSucceedWhenAtLeastOneDllFindsRuntimeProvider(Runne [TestMethod] // This is a built-in assembly filter test. It changes with vstest.version, so testing against 1 version of console is enough. - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: Net)] public void RunXunitTestsWhenProvidingAllDllsInBin(RunnerInfo runnerInfo) { // This is the default filter of AzDo VSTest task: @@ -414,7 +414,7 @@ public void RunXunitTestsWhenProvidingAllDllsInBin(RunnerInfo runnerInfo) [TestMethod] // This is a built-in assembly filter test. It changes with vstest.version, so testing against 1 version of console is enough. - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: Net)] public void RunMstestTestsWhenProvidingAllDllsInBin(RunnerInfo runnerInfo) { // This is the default filter of AzDo VSTest task: @@ -440,7 +440,7 @@ public void RunMstestTestsWhenProvidingAllDllsInBin(RunnerInfo runnerInfo) [TestMethod] // This is a built-in assembly filter test. It changes with vstest.version, so testing against 1 version of console is enough. - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: Net)] public void RunNunitTestsWhenProvidingAllDllsInBin(RunnerInfo runnerInfo) { // This is the default filter of AzDo VSTest task: @@ -465,7 +465,7 @@ public void RunNunitTestsWhenProvidingAllDllsInBin(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: Net)] public void RunTestsWhenProvidingJustPlatformDllsFailsTheRun(RunnerInfo runnerInfo) { // This is the default filter of AzDo VSTest task: diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ExecutionThreadApartmentStateTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ExecutionThreadApartmentStateTests.cs index 05163ddd73..f0550ba485 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ExecutionThreadApartmentStateTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ExecutionThreadApartmentStateTests.cs @@ -11,7 +11,7 @@ namespace Microsoft.TestPlatform.AcceptanceTests; public class ExecutionThreadApartmentStateTests : AcceptanceTestBase { [TestMethod] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] + [TestMatrix(testHost: NetFx, inProcess: true)] public void UITestShouldPassIfApartmentStateIsSTA(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -24,7 +24,7 @@ public void UITestShouldPassIfApartmentStateIsSTA(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void WarningShouldBeShownWhenValueIsSTAForNetCore(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -39,7 +39,7 @@ public void WarningShouldBeShownWhenValueIsSTAForNetCore(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] + [TestMatrix(testHost: NetFx, inProcess: true)] public void UITestShouldFailWhenDefaultApartmentStateIsMTA(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -54,7 +54,7 @@ public void UITestShouldFailWhenDefaultApartmentStateIsMTA(RunnerInfo runnerInfo [Ignore(@"Issue with TestSessionTimeout: https://github.com/Microsoft/vstest/issues/980")] [TestMethod] - [NetFullTargetFrameworkDataSource(inIsolation: true, inProcess: true)] + [TestMatrix(testHost: NetFx, inProcess: true)] public void CancelTestExectionShouldWorkWhenApartmentStateIsSTA(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FilePatternParserTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FilePatternParserTests.cs index 4365382eb3..509773212c 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FilePatternParserTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FilePatternParserTests.cs @@ -12,7 +12,7 @@ namespace Microsoft.TestPlatform.AcceptanceTests; public class FilePatternParserTests : AcceptanceTestBase { [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void WildCardPatternShouldCorrectlyWorkOnFiles(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -31,7 +31,7 @@ public void WildCardPatternShouldCorrectlyWorkOnFiles(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void WildCardPatternShouldCorrectlyWorkOnArbitraryDepthDirectories(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -56,7 +56,7 @@ public void WildCardPatternShouldCorrectlyWorkOnArbitraryDepthDirectories(Runner } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void WildCardPatternShouldCorrectlyWorkForRelativeAssemblyPath(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -81,7 +81,7 @@ public void WildCardPatternShouldCorrectlyWorkForRelativeAssemblyPath(RunnerInfo } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void WildCardPatternShouldCorrectlyWorkOnMultipleFiles(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FrameworkTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FrameworkTests.cs index 12b9f7f676..9cba806667 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FrameworkTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/FrameworkTests.cs @@ -13,7 +13,7 @@ public class FrameworkTests : AcceptanceTestBase { [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void FrameworkArgumentShouldWork(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -26,7 +26,7 @@ public void FrameworkArgumentShouldWork(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void FrameworkShortNameArgumentShouldWork(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -41,8 +41,8 @@ public void FrameworkShortNameArgumentShouldWork(RunnerInfo runnerInfo) [TestMethod] // framework runner not available on Linux [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource(useCoreRunner: false)] - //[NetCoreTargetFrameworkDataSource] + [TestMatrix(console: NetFx, testHost: NetFx)] + //[TestMatrix(testHost: Net)] public void OnWrongFrameworkPassedTestRunShouldNotRun(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -65,8 +65,8 @@ public void OnWrongFrameworkPassedTestRunShouldNotRun(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] + [TestMatrix(testHost: Net)] // The .NET (Core) runner produces a different framework-incompatible warning on non-Windows, so keep this Windows-only. [TestCategory("Windows-Review")] public void RunSpecificTestsShouldWorkWithFrameworkInCompatibleWarning(RunnerInfo runnerInfo) diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/GlobalUsings.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/GlobalUsings.cs new file mode 100644 index 0000000000..6e5cf8264e --- /dev/null +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/GlobalUsings.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// Brings the Target enum members (Both, NetFx, Net) into scope unqualified so [TestMatrix] call sites +// read as e.g. [TestMatrix(console: NetFx, testHost: Net)]. The console:/testHost: parameter names +// disambiguate which axis each value applies to. +global using static Microsoft.TestPlatform.TestUtilities.Target; diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ListExtensionsTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ListExtensionsTests.cs index 10b445b904..cf027268cb 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ListExtensionsTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ListExtensionsTests.cs @@ -12,7 +12,7 @@ namespace Microsoft.TestPlatform.AcceptanceTests; public class ListExtensionsTests : AcceptanceTestBase { [TestMethod] - [NetFullTargetFrameworkDataSource(inIsolation: false, inProcess: true)] + [TestMatrix(testHost: NetFx, inIsolation: false, inProcess: true)] public void ListDiscoverersShouldShowInboxDiscoverers(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -33,7 +33,7 @@ public void ListDiscoverersShouldShowInboxDiscoverers(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource(inIsolation: false, inProcess: true)] + [TestMatrix(testHost: NetFx, inIsolation: false, inProcess: true)] public void ListExecutorsShouldShowInboxExecutors(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -56,7 +56,7 @@ public void ListExecutorsShouldShowInboxExecutors(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource(inIsolation: false, inProcess: true)] + [TestMatrix(testHost: NetFx, inIsolation: false, inProcess: true)] public void ListLoggersShouldShowInboxLoggers(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -68,7 +68,7 @@ public void ListLoggersShouldShowInboxLoggers(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource(inIsolation: false, inProcess: true)] + [TestMatrix(testHost: NetFx, inIsolation: false, inProcess: true)] public void ListSettingsProvidersShouldShowInboxSettingsProviders(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/LoggerTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/LoggerTests.cs index 86717def09..a26da60d03 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/LoggerTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/LoggerTests.cs @@ -16,7 +16,7 @@ namespace Microsoft.TestPlatform.AcceptanceTests; public class LoggerTests : AcceptanceTestBase { [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void TrxLoggerWithFriendlyNameShouldProperlyOverwriteFile(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -36,7 +36,7 @@ public void TrxLoggerWithFriendlyNameShouldProperlyOverwriteFile(RunnerInfo runn } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void HtmlLoggerWithFriendlyNameShouldProperlyOverwriteFile(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -56,7 +56,7 @@ public void HtmlLoggerWithFriendlyNameShouldProperlyOverwriteFile(RunnerInfo run } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void HtmlLoggerWithFriendlyNameContainsExpectedContent(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -83,7 +83,7 @@ private static XmlDocument LoadReport(string htmlLogFilePath) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void TrxLoggerWithExecutorUriShouldProperlyOverwriteFile(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -103,7 +103,7 @@ public void TrxLoggerWithExecutorUriShouldProperlyOverwriteFile(RunnerInfo runne } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void TrxLoggerWithLogFilePrefixShouldGenerateMultipleTrx(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -123,7 +123,7 @@ public void TrxLoggerWithLogFilePrefixShouldGenerateMultipleTrx(RunnerInfo runne } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void HtmlLoggerWithExecutorUriShouldProperlyOverwriteFile(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -143,7 +143,7 @@ public void HtmlLoggerWithExecutorUriShouldProperlyOverwriteFile(RunnerInfo runn } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void TrxLoggerResultSummaryOutcomeValueShouldBeFailedIfNoTestsExecutedAndTreatNoTestsAsErrorIsTrue(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -165,7 +165,7 @@ public void TrxLoggerResultSummaryOutcomeValueShouldBeFailedIfNoTestsExecutedAnd } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void TrxLoggerResultSummaryOutcomeValueShouldNotChangeIfNoTestsExecutedAndTreatNoTestsAsErrorIsFalse(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -187,7 +187,7 @@ public void TrxLoggerResultSummaryOutcomeValueShouldNotChangeIfNoTestsExecutedAn } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void TrxLoggerResultSummaryOutcomeValueShouldBeFailedWhenDataCollectorLogsError(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -215,7 +215,7 @@ public void TrxLoggerResultSummaryOutcomeValueShouldBeFailedWhenDataCollectorLog } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void TrxLoggerResultSummaryOutcomeValueShouldBeCompletedWhenDataCollectorLogsErrorAndTreatErrorMessagesAsWarningsIsTrue(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -307,7 +307,7 @@ private static void IsFileAndContentEqual(string filePath) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void TrxLoggerShouldNotDoubleCountDataDrivenTestResults(RunnerInfo runnerInfo) { // Regression test for https://github.com/microsoft/vstest/issues/15643 @@ -338,7 +338,7 @@ public void TrxLoggerShouldNotDoubleCountDataDrivenTestResults(RunnerInfo runner } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void TrxLoggerShouldPlaceTrxFileInSubdirectoryWhenLogFileNameContainsPath(RunnerInfo runnerInfo) { // Regression test for https://github.com/microsoft/vstest/issues/15271 diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MissingTestSdkTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MissingTestSdkTests.cs index 1cd06e6b33..01b28a48ae 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MissingTestSdkTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MissingTestSdkTests.cs @@ -19,7 +19,7 @@ public class MissingTestSdkTests : AcceptanceTestBase // silently run on the built-in testhost shipped next to the runner (that fallback is for native C++ runners) - the // run should fail and tell the user to reference Microsoft.NET.Test.Sdk. [TestMethod] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false, useCoreRunner: true)] + [TestMatrix(console: Net, testHost: Net)] public void RunningManagedProjectWithoutTestSdkShouldFailAndSuggestTestSdk(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -42,7 +42,7 @@ public void RunningManagedProjectWithoutTestSdkShouldFailAndSuggestTestSdk(Runne // build C++ locally. Windows-only (the asset is a Windows native dll). [TestMethod] [TestCategory("Windows-Review")] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false, useCoreRunner: true)] + [TestMatrix(console: Net, testHost: Net)] public void RunningNativeCppProjectWithoutTestSdkShouldUseTheBuiltInTesthostFallback(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MultitargetingTestHostTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MultitargetingTestHostTests.cs index 163238e783..3cb9cf66ac 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MultitargetingTestHostTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MultitargetingTestHostTests.cs @@ -17,8 +17,8 @@ public class MultitargetingTestHostTests : AcceptanceTestBase [TestCategory("Windows-Review")] // the underlying test is using xUnit to avoid AppDomain enhancements in MSTest that make this pass even without multitargetting // xUnit supports net452 onwards, so that is why this starts at net452, I also don't test all framework versions - [NetCoreRunner(NETFX)] - [NetFrameworkRunner(NETFX)] + [TestMatrix(console: Net, testHost: NetFx)] + [TestMatrix(console: NetFx, testHost: NetFx)] public void TestRunInATesthostThatTargetsTheirChosenNETFramework(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/PlatformTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/PlatformTests.cs index e9a5eb2dad..84fe0551d2 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/PlatformTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/PlatformTests.cs @@ -15,8 +15,8 @@ public class PlatformTests : AcceptanceTestBase /// The run test execution with platform x64. /// [TestMethod] - [NetFullTargetFrameworkDataSource] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] + [TestMatrix(testHost: Net)] public void RunTestExecutionWithPlatformx64(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -29,8 +29,8 @@ public void RunTestExecutionWithPlatformx64(RunnerInfo runnerInfo) /// The run test execution with platform x86. /// [TestMethod] - [NetFullTargetFrameworkDataSource] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] + [TestMatrix(testHost: Net)] public void RunTestExecutionWithPlatformx86(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/PortableNugetPackageTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/PortableNugetPackageTests.cs index 1b42e0b8f0..862eed16ac 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/PortableNugetPackageTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/PortableNugetPackageTests.cs @@ -24,7 +24,7 @@ public static void ClassInit(TestContext _) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void RunMultipleTestAssemblies(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -38,7 +38,7 @@ public void RunMultipleTestAssemblies(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void DiscoverAllTests(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ProcessesInteractionTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ProcessesInteractionTests.cs index 9fe2537ebe..6f1034fec7 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ProcessesInteractionTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ProcessesInteractionTests.cs @@ -17,7 +17,7 @@ public class ProcessesInteractionTests : AcceptanceTestBase /// flush its output and error streams).See https://github.com/microsoft/vstest/issues/3375 /// [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void WhenTestHostProcessExitsBecauseTheTargetedRuntimeIsNoFoundThenTheMessageIsCapturedFromTheErrorOutput(RunnerInfo runnerInfo) { // Arrange diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RecursiveResourcesLookupTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RecursiveResourcesLookupTests.cs index 6bdf038ef8..f6e2736c1b 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RecursiveResourcesLookupTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RecursiveResourcesLookupTests.cs @@ -14,7 +14,7 @@ public class RecursiveResourcesLookupTests : AcceptanceTestBase // two different runners. The NetFull data source is empty on Linux/macOS. [TestCategory("Windows-Review")] [Ignore("Temporarily ignore until solving https://github.com/microsoft/testfx/issues/2692")] - [NetFullTargetFrameworkDataSource(useCoreRunner: false)] + [TestMatrix(console: NetFx, testHost: NetFx)] public void RunsToCompletionWhenJapaneseResourcesAreLookedUpForMSCorLib(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ResultsDirectoryTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ResultsDirectoryTests.cs index 76e6937772..d6e5c5d5cf 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ResultsDirectoryTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ResultsDirectoryTests.cs @@ -13,7 +13,7 @@ namespace Microsoft.TestPlatform.AcceptanceTests; public class ResultsDirectoryTests : AcceptanceTestBase { [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void TrxFileShouldBeCreatedInResultsDirectory(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -33,7 +33,7 @@ public void TrxFileShouldBeCreatedInResultsDirectory(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void ResultsDirectoryRelativePathShouldWork(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RunsettingsTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RunsettingsTests.cs index 2816b7e9b4..0930dddc81 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RunsettingsTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/RunsettingsTests.cs @@ -20,8 +20,8 @@ public class RunsettingsTests : AcceptanceTestBase /// Command line run settings should have high precedence among settings file, cli runsettings and cli switches /// [TestMethod] - [NetFullTargetFrameworkDataSource] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] + [TestMatrix(testHost: Net)] public void CommandLineRunSettingsShouldWinAmongAllOptions(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -60,8 +60,8 @@ public void CommandLineRunSettingsShouldWinAmongAllOptions(RunnerInfo runnerInfo /// Command line run settings should have high precedence between cli runsettings and cli switches. /// [TestMethod] - [NetFullTargetFrameworkDataSource] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] + [TestMatrix(testHost: Net)] public void CLIRunsettingsShouldWinBetweenCLISwitchesAndCLIRunsettings(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -94,8 +94,8 @@ public void CLIRunsettingsShouldWinBetweenCLISwitchesAndCLIRunsettings(RunnerInf /// Command line switches should have high precedence if runsetting file and command line switch specified /// [TestMethod] - [NetFullTargetFrameworkDataSource] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] + [TestMatrix(testHost: Net)] public void CommandLineSwitchesShouldWinBetweenSettingsFileAndCommandLineSwitches(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -123,8 +123,8 @@ public void CommandLineSwitchesShouldWinBetweenSettingsFileAndCommandLineSwitche #endregion [TestMethod] - [NetFullTargetFrameworkDataSource] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] + [TestMatrix(testHost: Net)] public void RunSettingsWithoutParallelAndPlatformX86(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -148,8 +148,8 @@ public void RunSettingsWithoutParallelAndPlatformX86(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] + [TestMatrix(testHost: Net)] public void RunSettingsParamsAsArguments(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -175,8 +175,8 @@ public void RunSettingsParamsAsArguments(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] + [TestMatrix(testHost: Net)] public void RunSettingsAndRunSettingsParamsAsArguments(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -210,8 +210,8 @@ public void RunSettingsAndRunSettingsParamsAsArguments(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSource] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] + [TestMatrix(testHost: Net)] public void RunSettingsWithParallelAndPlatformX64(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -231,8 +231,8 @@ public void RunSettingsWithParallelAndPlatformX64(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSourceAttribute(inIsolation: true, inProcess: true)] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx, inProcess: true)] + [TestMatrix(testHost: Net)] public void RunSettingsWithInvalidValueShouldLogError(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -252,8 +252,8 @@ public void RunSettingsWithInvalidValueShouldLogError(RunnerInfo runnerInfo) } [TestMethod] - [NetFullTargetFrameworkDataSourceAttribute(inIsolation: true, inProcess: true)] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx, inProcess: true)] + [TestMatrix(testHost: Net)] public void TestAdapterPathFromRunSettings(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -275,8 +275,8 @@ public void TestAdapterPathFromRunSettings(RunnerInfo runnerInfo) #region RunSettings With EnvironmentVariables Settings Tests [TestMethod] - [NetFullTargetFrameworkDataSource] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] + [TestMatrix(testHost: Net)] public void EnvironmentVariablesSettingsShouldSetEnvironmentVariables(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -313,8 +313,8 @@ public void EnvironmentVariablesSettingsShouldSetEnvironmentVariables(RunnerInfo /// /// [TestMethod] - [NetFullTargetFrameworkDataSourceAttribute(useDesktopRunner: false)] - [NetCoreTargetFrameworkDataSourceAttribute(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: NetFx)] + [TestMatrix(console: Net, testHost: Net)] public void RunSettingsAreLoadedFromProject(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SelfContainedAppTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SelfContainedAppTests.cs index b236e6e507..8a6c8fc5ba 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SelfContainedAppTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SelfContainedAppTests.cs @@ -13,7 +13,7 @@ public class SelfContainedAppTests : AcceptanceTestBase { [TestMethod] [TestCategory("Windows-Review")] - [NetCoreTargetFrameworkDataSourceAttribute(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: Net)] public void RunningApplicationThatIsBuiltAsSelfContainedWillNotFailToFindHostpolicyDll(RunnerInfo runnerInfo) { // when the application is self-contained which is dictated by the RuntimeIdentifier and OutputType project diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SerializationCompatibilityTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SerializationCompatibilityTests.cs index 9cb64cea20..f9197a62f6 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SerializationCompatibilityTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SerializationCompatibilityTests.cs @@ -40,7 +40,7 @@ public class SerializationCompatibilityTests : AcceptanceTestBase [TestMethod] // Compatibility matrix includes the .NET Framework runner/testhost, which is not available on Linux/macOS. [TestCategory("Windows-Review")] - [RunnerCompatibilityDataSource()] + [CompatibilityMatrix(CompatScenario.VSTestConsole)] public void DiscoverTests_LatestRunner_WithOlderTesthosts(RunnerInfo runnerInfo) { #pragma warning disable RS0030 // Do not use banned APIs @@ -76,7 +76,7 @@ public void DiscoverTests_LatestRunner_WithOlderTesthosts(RunnerInfo runnerInfo) [TestMethod] // Compatibility matrix includes the .NET Framework runner/testhost, which is not available on Linux/macOS. [TestCategory("Windows-Review")] - [TestHostCompatibilityDataSource] + [CompatibilityMatrix(CompatScenario.TestHost)] public void DiscoverTests_OlderRunners_WithLatestTesthost(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -113,7 +113,7 @@ public void DiscoverTests_OlderRunners_WithLatestTesthost(RunnerInfo runnerInfo) [TestMethod] // Compatibility matrix includes the .NET Framework runner/testhost, which is not available on Linux/macOS. [TestCategory("Windows-Review")] - [RunnerCompatibilityDataSource] + [CompatibilityMatrix(CompatScenario.VSTestConsole)] public void RunTests_LatestRunner_WithOlderTesthosts(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -153,7 +153,7 @@ public void RunTests_LatestRunner_WithOlderTesthosts(RunnerInfo runnerInfo) [TestMethod] // Compatibility matrix includes the .NET Framework runner/testhost, which is not available on Linux/macOS. [TestCategory("Windows-Review")] - [TestHostCompatibilityDataSource] + [CompatibilityMatrix(CompatScenario.TestHost)] public void RunTests_OlderRunners_WithLatestTesthost(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SerializerSelectionTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SerializerSelectionTests.cs index b21d517bd2..9225e6049d 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SerializerSelectionTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/SerializerSelectionTests.cs @@ -10,7 +10,7 @@ namespace Microsoft.TestPlatform.AcceptanceTests; public class SerializerSelectionTests : AcceptanceTestBase { [TestMethod] - [NetCoreRunner(Core11TargetFramework)] + [TestMatrix(console: Net, testHost: Net)] public void OnNetCoreRunner_ShouldUseSystemTextJson(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -28,7 +28,7 @@ public void OnNetCoreRunner_ShouldUseSystemTextJson(RunnerInfo runnerInfo) // The .NET Framework runner (and its Jsonite serializer) only runs on Windows; the core counterpart // is covered by OnNetCoreRunner_ShouldUseSystemTextJson. [TestCategory("Windows-Review")] - [NetFrameworkRunner(Net481TargetFramework)] + [TestMatrix(console: NetFx, testHost: NetFx)] public void OnNetFrameworkRunner_ShouldUseJsonite(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TelemetryTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TelemetryTests.cs index a972b9a62c..6d27b01f27 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TelemetryTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TelemetryTests.cs @@ -20,7 +20,7 @@ public class TelemetryTests : AcceptanceTestBase private const string LOG_TELEMETRY_PATH = "VSTEST_LOGTELEMETRY_PATH"; [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void RunTestsShouldPublishMetrics(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -29,7 +29,7 @@ public void RunTestsShouldPublishMetrics(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void DiscoverTestsShouldPublishMetrics(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TestCaseFilterTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TestCaseFilterTests.cs index 32323d9a54..47a974b228 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TestCaseFilterTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TestCaseFilterTests.cs @@ -10,7 +10,7 @@ namespace Microsoft.TestPlatform.AcceptanceTests; public class TestCaseFilterTests : AcceptanceTestBase { [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void RunSelectedTestsWithAndOperatorTrait(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -26,7 +26,7 @@ public void RunSelectedTestsWithAndOperatorTrait(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void RunSelectedTestsWithCategoryTraitInMixCase(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -42,7 +42,7 @@ public void RunSelectedTestsWithCategoryTraitInMixCase(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void RunSelectedTestsWithClassNameTrait(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -58,7 +58,7 @@ public void RunSelectedTestsWithClassNameTrait(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void RunSelectedTestsWithFullyQualifiedNameTrait(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -76,7 +76,7 @@ public void RunSelectedTestsWithFullyQualifiedNameTrait(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void RunSelectedTestsWithNameTrait(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -92,7 +92,7 @@ public void RunSelectedTestsWithNameTrait(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void RunSelectedTestsWithOrOperatorTrait(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -108,7 +108,7 @@ public void RunSelectedTestsWithOrOperatorTrait(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void RunSelectedTestsWithPriorityTrait(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -128,7 +128,7 @@ public void RunSelectedTestsWithPriorityTrait(RunnerInfo runnerInfo) /// this command should provide same results as /TestCaseFilter:"FullyQualifiedName~UnitTest1". /// [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void TestCaseFilterShouldWorkIfOnlyPropertyValueGivenInExpression(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -150,7 +150,7 @@ public void TestCaseFilterShouldWorkIfOnlyPropertyValueGivenInExpression(RunnerI [TestCategory("Windows-Review")] // MSTest v1 tests from dlls are only supported in .NET Framework runner, in and outside of VS // via Microsoft.VisualStudio.TestPlatform.Extensions.VSTestIntegration.dll - [NetFullTargetFrameworkDataSource(useCoreRunner: false)] + [TestMatrix(console: NetFx, testHost: NetFx)] public void DiscoverMstestV1TestsWithAndOperatorTrait(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -171,7 +171,7 @@ public void DiscoverMstestV1TestsWithAndOperatorTrait(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void RunSelectedTestsWithNoneTestCategoryFilterMatchesUncategorizedTests(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -190,7 +190,7 @@ public void RunSelectedTestsWithNoneTestCategoryFilterMatchesUncategorizedTests( } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void RunSelectedTestsWithNoneTestCategoryNotEqualFilterMatchesCategorizedTests(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TestPlatformNugetPackageTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TestPlatformNugetPackageTests.cs index 498e515642..4e7c2d979a 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TestPlatformNugetPackageTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/TestPlatformNugetPackageTests.cs @@ -22,8 +22,8 @@ public static void ClassInit(TestContext _) [TestMethod] [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSourceAttribute(useCoreRunner: false)] - [NetCoreTargetFrameworkDataSourceAttribute(useCoreRunner: false)] + [TestMatrix(console: NetFx, testHost: NetFx)] + [TestMatrix(console: NetFx, testHost: Net)] public void RunMultipleTestAssembliesWithCodeCoverage(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/VideoRecorderTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/VideoRecorderTests.cs index e738e84c5d..41cfe5ed29 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/VideoRecorderTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/VideoRecorderTests.cs @@ -15,7 +15,7 @@ public class VideoRecorderTests : AcceptanceTestBase { [Ignore("Video recording is flaky in CI — screen recorder fails to establish communication. See #15586.")] [TestMethod] - [NetFullTargetFrameworkDataSource(useCoreRunner: false, useVsixRunner: true)] + [TestMatrix(console: NetFx, testHost: NetFx, vsix: true)] public void VideoRecorderDataCollectorShouldRecordVideoWithRunSettings(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/AppDomainTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/AppDomainTests.cs index f0b67c9b96..3ed6ac900a 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/AppDomainTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/AppDomainTests.cs @@ -22,7 +22,7 @@ public class AppDomainTests : AcceptanceTestBase [TestMethod] [TestCategory("Windows-Review")] // AppDomains are .NET Framework only, run in .NET Framework runner and .NET runner - [NetFullTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] public void RunTestExecutionWithDisableAppDomain(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/FilterSourceIntegrationTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/FilterSourceIntegrationTests.cs index f7a9c725ac..0a762aad58 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/FilterSourceIntegrationTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/FilterSourceIntegrationTests.cs @@ -16,7 +16,7 @@ namespace Microsoft.TestPlatform.Library.IntegrationTests; public class FilterSourceIntegrationTests : AcceptanceTestBase { [TestMethod] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: Net)] public void FilterSourcePackage_AllTestsPass(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/GlobalUsings.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/GlobalUsings.cs new file mode 100644 index 0000000000..6e5cf8264e --- /dev/null +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/GlobalUsings.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// Brings the Target enum members (Both, NetFx, Net) into scope unqualified so [TestMatrix] call sites +// read as e.g. [TestMatrix(console: NetFx, testHost: Net)]. The console:/testHost: parameter names +// disambiguate which axis each value applies to. +global using static Microsoft.TestPlatform.TestUtilities.Target; diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/CodeCoverageTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/CodeCoverageTests.cs index 1534d45823..5c0ff04c41 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/CodeCoverageTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/CodeCoverageTests.cs @@ -49,7 +49,7 @@ public void Cleanup() } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void TestRunWithCodeCoverage(RunnerInfo runnerInfo) { // arrange @@ -74,7 +74,7 @@ public void TestRunWithCodeCoverage(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void TestRunWithCodeCoverageUsingClrIe(RunnerInfo runnerInfo) { // arrange @@ -99,7 +99,7 @@ public void TestRunWithCodeCoverageUsingClrIe(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void TestRunWithCodeCoverageParallel(RunnerInfo runnerInfo) { // arrange @@ -122,12 +122,12 @@ public void TestRunWithCodeCoverageParallel(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public async Task TestRunWithCodeCoverageAndAttachmentsProcessingWithInvokedDataCollectors(RunnerInfo runnerInfo) => await TestRunWithCodeCoverageAndAttachmentsProcessingInternal(runnerInfo, true); [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public async Task TestRunWithCodeCoverageAndAttachmentsProcessingWithoutInvokedDataCollectors(RunnerInfo runnerInfo) => await TestRunWithCodeCoverageAndAttachmentsProcessingInternal(runnerInfo, false); @@ -186,7 +186,7 @@ await _vstestConsoleWrapper.ProcessTestRunAttachmentsAsync( } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public async Task TestRunWithCodeCoverageAndAttachmentsProcessingNoMetrics(RunnerInfo runnerInfo) { // System.Environment.SetEnvironmentVariable("VSTEST_RUNNER_DEBUG_ATTACHVS", "1"); @@ -239,7 +239,7 @@ public async Task TestRunWithCodeCoverageAndAttachmentsProcessingNoMetrics(Runne } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public async Task TestRunWithCodeCoverageAndAttachmentsProcessingModuleDuplicated(RunnerInfo runnerInfo) { // arrange @@ -296,7 +296,7 @@ public async Task TestRunWithCodeCoverageAndAttachmentsProcessingModuleDuplicate } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public async Task TestRunWithCodeCoverageAndAttachmentsProcessingSameReportFormat(RunnerInfo runnerInfo) { // arrange @@ -359,7 +359,7 @@ public async Task TestRunWithCodeCoverageAndAttachmentsProcessingSameReportForma } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public async Task TestRunWithCodeCoverageAndAttachmentsProcessingDifferentReportFormats(RunnerInfo runnerInfo) { // arrange @@ -422,7 +422,7 @@ public async Task TestRunWithCodeCoverageAndAttachmentsProcessingDifferentReport } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public async Task EndSessionShouldEnsureVstestConsoleProcessDies(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/CustomTestHostLauncherTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/CustomTestHostLauncherTests.cs index 9a3f86259b..3d9fae9b18 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/CustomTestHostLauncherTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/CustomTestHostLauncherTests.cs @@ -37,7 +37,7 @@ public void Cleanup() [TestMethod] // WrapperCompatibilityDataSource includes the .NET Framework runner, which is not available on Linux/macOS. [TestCategory("Windows-Review")] - [WrapperCompatibilityDataSource()] + [CompatibilityMatrix(CompatScenario.Wrapper)] public void RunTestsWithCustomTestHostLauncherAttachesToDebuggerUsingTheProvidedLauncher(RunnerInfo runnerInfo) { // Arrange @@ -61,7 +61,7 @@ public void RunTestsWithCustomTestHostLauncherAttachesToDebuggerUsingTheProvided // WrapperCompatibilityDataSource includes the .NET Framework runner, which is not available on Linux/macOS. [TestCategory("Windows-Review")] [TestCategory("Feature")] - [WrapperCompatibilityDataSource] + [CompatibilityMatrix(CompatScenario.Wrapper)] public void RunAllTestsWithMixedTFMsWillProvideAdditionalInformationToTheDebugger(RunnerInfo runnerInfo) { // Arrange diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DataCollectorAttachmentProcessor.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DataCollectorAttachmentProcessor.cs index d85cb80977..8ae2849ae3 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DataCollectorAttachmentProcessor.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DataCollectorAttachmentProcessor.cs @@ -44,7 +44,7 @@ public void Cleanup() } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public async Task AttachmentProcessorDataCollector_ExtensionFileNotLocked(RunnerInfo runnerInfo) { // arrange diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DifferentTestFrameworkSimpleTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DifferentTestFrameworkSimpleTests.cs index cd1c7ad13a..35c1c39eee 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DifferentTestFrameworkSimpleTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DifferentTestFrameworkSimpleTests.cs @@ -39,7 +39,7 @@ public void Cleanup() [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void RunTestsWithNunitAdapter(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -71,7 +71,7 @@ public void RunTestsWithNunitAdapter(RunnerInfo runnerInfo) // The xUnit adapter produces no results on Linux/macOS (diagnostic log shows a NullReferenceException because path is null), // so the run returns an empty sequence and .First() throws. Keep this Windows-only. [TestCategory("Windows-Review")] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void RunTestsWithXunitAdapter(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -107,7 +107,7 @@ public void RunTestsWithXunitAdapter(RunnerInfo runnerInfo) [TestMethod] // TODO: this does not work with netcore testhost, why? [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] public void RunTestsWithNonDllAdapter(RunnerInfo runnerInfo) { // This used to be test for Chutzpah, but it has long running problem with updating dependencies, diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DiscoverTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DiscoverTests.cs index 190079f3a3..9f826d3b23 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DiscoverTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/DiscoverTests.cs @@ -46,7 +46,7 @@ public void Cleanup() [TestMethod] // WrapperCompatibilityDataSource includes the .NET Framework runner, which is not available on Linux/macOS. [TestCategory("Windows-Review")] - [WrapperCompatibilityDataSource] + [CompatibilityMatrix(CompatScenario.Wrapper)] public void DiscoverTestsUsingDiscoveryEventHandler1(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -65,7 +65,7 @@ public void DiscoverTestsUsingDiscoveryEventHandler1(RunnerInfo runnerInfo) [TestMethod] // WrapperCompatibilityDataSource includes the .NET Framework runner, which is not available on Linux/macOS. [TestCategory("Windows-Review")] - [WrapperCompatibilityDataSource] + [CompatibilityMatrix(CompatScenario.Wrapper)] public void DiscoverTestsUsingDiscoveryEventHandler2AndTelemetryOptedOut(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -88,7 +88,7 @@ public void DiscoverTestsUsingDiscoveryEventHandler2AndTelemetryOptedOut(RunnerI [TestMethod] [TestCategory("Smoke")] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void DiscoverTestsUsingDiscoveryEventHandler2AndTelemetryOptedIn(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -106,7 +106,7 @@ public void DiscoverTestsUsingDiscoveryEventHandler2AndTelemetryOptedIn(RunnerIn } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void DiscoverTestsUsingEventHandler2AndBatchSize(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -135,7 +135,7 @@ public void DiscoverTestsUsingEventHandler2AndBatchSize(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void DiscoverTestsUsingEventHandler1AndBatchSize(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -163,7 +163,7 @@ public void DiscoverTestsUsingEventHandler1AndBatchSize(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void DiscoverTestUsingEventHandler2ShouldContainAllSourcesAsFullyDiscovered(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -187,8 +187,8 @@ public void DiscoverTestUsingEventHandler2ShouldContainAllSourcesAsFullyDiscover // We run .NET Runner -> .NET Testhost and .NET Framework Runner -> .NET Frameworks Testhost. // The .NET Framework runner/testhost is not available on Linux/macOS. [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource(useCoreRunner: false)] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: NetFx, testHost: NetFx)] + [TestMatrix(console: Net, testHost: Net)] public void DiscoverTestsUsingSourceNavigation(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -208,7 +208,7 @@ public void DiscoverTestsUsingSourceNavigation(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] [Ignore("Flaky on CI")] public async Task CancelTestDiscovery(RunnerInfo runnerInfo) { diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/LiveUnitTestingTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/LiveUnitTestingTests.cs index 38e5f2a347..a0989ce895 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/LiveUnitTestingTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/LiveUnitTestingTests.cs @@ -38,7 +38,7 @@ public void Cleanup() [TestMethod] // Touches appdomain settings, preferring .NET Framework testhost here. [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] public void DiscoverTestsUsingLiveUnitTesting(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -64,7 +64,7 @@ public void DiscoverTestsUsingLiveUnitTesting(RunnerInfo runnerInfo) [TestMethod] // Touches appdomain settings, preferring .NET Framework testhost here. [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource] + [TestMatrix(testHost: NetFx)] public void RunTestsWithLiveUnitTesting(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunSelectedTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunSelectedTests.cs index 2357b9f7cc..2aeca39584 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunSelectedTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunSelectedTests.cs @@ -37,7 +37,7 @@ public void Cleanup() } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void RunSelectedTestsWithoutTestPlatformOptions(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -56,7 +56,7 @@ public void RunSelectedTestsWithoutTestPlatformOptions(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void RunSelectedTestsWithTestPlatformOptions(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTests.cs index 7975ecd58e..b1236c573e 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTests.cs @@ -48,7 +48,7 @@ public void Cleanup() [TestMethod] // WrapperCompatibilityDataSource includes the .NET Framework runner, which is not available on Linux/macOS. [TestCategory("Windows-Review")] - [WrapperCompatibilityDataSource] + [CompatibilityMatrix(CompatScenario.Wrapper)] public void RunAllTests(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -65,7 +65,7 @@ public void RunAllTests(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] [TestCategory("Smoke")] public void RunAllTestsFromDlls(RunnerInfo runnerInfo) { @@ -85,7 +85,7 @@ public void RunAllTestsFromDlls(RunnerInfo runnerInfo) [TestMethod] // WrapperCompatibilityDataSource includes the .NET Framework runner, which is not available on Linux/macOS. [TestCategory("Windows-Review")] - [WrapperCompatibilityDataSource()] + [CompatibilityMatrix(CompatScenario.Wrapper)] public void RunAllTestsWithMixedTFMsWillRunTestsFromAllProvidedDllEvenWhenTheyMixTFMs(RunnerInfo runnerInfo) { // Arrange @@ -110,7 +110,7 @@ public void RunAllTestsWithMixedTFMsWillRunTestsFromAllProvidedDllEvenWhenTheyMi } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void EndSessionShouldEnsureVstestConsoleProcessDies(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -130,7 +130,7 @@ public void EndSessionShouldEnsureVstestConsoleProcessDies(RunnerInfo runnerInfo } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void RunTestsWithTelemetryOptedIn(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -153,7 +153,7 @@ public void RunTestsWithTelemetryOptedIn(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void RunTestsWithTelemetryOptedOut(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -175,8 +175,8 @@ public void RunTestsWithTelemetryOptedOut(RunnerInfo runnerInfo) // The assertion below branches on the .NET Framework-specific stack overflow message, and the // .NET Framework testhost is only available on Windows, so this runs as Windows-Review. [TestCategory("Windows-Review")] - [NetFullTargetFrameworkDataSource(useDesktopRunner: false)] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: NetFx)] + [TestMatrix(console: Net, testHost: Net)] public void RunTestsShouldThrowOnStackOverflowException(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -199,7 +199,7 @@ public void RunTestsShouldThrowOnStackOverflowException(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: Net)] public void RunTestsShouldShowProperWarningOnNoTestsForTestCaseFilter(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTestsWithDifferentConfigurationTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTestsWithDifferentConfigurationTests.cs index 9ac4c8f513..f67b22870c 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTestsWithDifferentConfigurationTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTestsWithDifferentConfigurationTests.cs @@ -44,7 +44,7 @@ public void Cleanup() } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void RunTestsWithTestAdapterPath(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -66,7 +66,7 @@ public void RunTestsWithTestAdapterPath(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void RunTestsWithRunSettingsWithParallel(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -98,7 +98,7 @@ public void RunTestsWithRunSettingsWithParallel(RunnerInfo runnerInfo) } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void RunTestsWithX64Source(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTestsWithFilterTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTestsWithFilterTests.cs index 3dc564da17..ee12ba1194 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTestsWithFilterTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/RunTestsWithFilterTests.cs @@ -39,7 +39,7 @@ public void Cleanup() [TestMethod] // WrapperCompatibilityDataSource includes the .NET Framework runner, which is not available on Linux/macOS. [TestCategory("Windows-Review")] - [WrapperCompatibilityDataSource] + [CompatibilityMatrix(CompatScenario.Wrapper)] public void RunTestsWithTestCaseFilter(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); @@ -62,7 +62,7 @@ public void RunTestsWithTestCaseFilter(RunnerInfo runnerInfo) [TestMethod] // Validates filter expression that is passed all the way down to testhost, unlikely that we will see difference in beharior between desktop and netcore runners. - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: Net)] public void RunTestsWithFastFilter(RunnerInfo runnerInfo) { SetTestEnvironment(_testEnvironment, runnerInfo); diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/SerializeTestRunTests.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/SerializeTestRunTests.cs index 87f27d6787..4839215644 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/SerializeTestRunTests.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/SerializeTestRunTests.cs @@ -48,7 +48,7 @@ public void Cleanup() [TestMethod] // This is testhost concept, try it on combination of testhosts, and .NET Runner. - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: Net)] public void DiscoverTestsAndRunTestsSequentially(RunnerInfo runnerInfo) { // Arrange @@ -69,7 +69,7 @@ public void DiscoverTestsAndRunTestsSequentially(RunnerInfo runnerInfo) [TestMethod] // This is testhost concept, try it on combination of testhosts, and .NET Runner. - [NetCoreTargetFrameworkDataSource(useDesktopRunner: false)] + [TestMatrix(console: Net, testHost: Net)] public void DiscoverTestsAndRunTestsSequentially_DisabledByFeatureFlag(RunnerInfo runnerInfo) { // Arrange @@ -90,7 +90,7 @@ public void DiscoverTestsAndRunTestsSequentially_DisabledByFeatureFlag(RunnerInf } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void DiscoverTestsAndRunTestsSequentially_IsNotSupportedForSources(RunnerInfo runnerInfo) { // Arrange diff --git a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/TargetFrameworkTestHostDemultiplexer.cs b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/TargetFrameworkTestHostDemultiplexer.cs index c3b2d7d1b9..ef5f6267f0 100644 --- a/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/TargetFrameworkTestHostDemultiplexer.cs +++ b/test/Microsoft.TestPlatform.Library.IntegrationTests/TranslationLayerTests/TargetFrameworkTestHostDemultiplexer.cs @@ -37,17 +37,17 @@ public void Cleanup() } [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void ExecuteContainerInMultiHost(RunnerInfo runnerInfo) => ExecuteContainerInMultiHost(runnerInfo, 3); [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void ExecuteContainerInMultiHost_MoreHostsThanTests(RunnerInfo runnerInfo) => ExecuteContainerInMultiHost(runnerInfo, 20); [TestMethod] - [NetCoreTargetFrameworkDataSource] + [TestMatrix(testHost: Net)] public void ExecuteSingleContainerInDefaultSingleHost(RunnerInfo runnerInfo) => ExecuteContainerInMultiHost(runnerInfo, -1); diff --git a/test/Microsoft.TestPlatform.TestUtilities/CompatibilityMatrixAttribute.cs b/test/Microsoft.TestPlatform.TestUtilities/CompatibilityMatrixAttribute.cs new file mode 100644 index 0000000000..65af1f2f12 --- /dev/null +++ b/test/Microsoft.TestPlatform.TestUtilities/CompatibilityMatrixAttribute.cs @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Reflection; + +namespace Microsoft.TestPlatform.TestUtilities; + +/// +/// Runs the test across a matrix of shipped vstest.console / testhost / MSTest-adapter versions to guard +/// backward compatibility. The component named by the scenario is pinned to the locally-built bits; +/// the other dimensions sweep a range of released versions. Each row is handed to the test as a . +/// Use the optional Before*/After*Feature properties to skip rows whose version predates / postdates a feature. +/// +public sealed class CompatibilityMatrixAttribute : CompatibilityDataSourceAttribute +{ + private readonly CompatibilityRowsBuilder _builder; + private readonly bool _withInProcess; + private readonly bool _withVsixRunner; + + /// + /// Initializes a new instance of the class. + /// + /// Which component's local changes are under test — the others sweep their compatible range. + public CompatibilityMatrixAttribute(CompatScenario scenario) + { + (string runnerVersions, string runnerFrameworks, string hostVersions, string hostFrameworks, string adapterVersions, _withInProcess, _withVsixRunner) = scenario switch + { + // Locally-built vstest.console against the range of shipped testhost versions (adds in-process and VSIX console rows). + CompatScenario.VSTestConsole => ( + AcceptanceTestBase.LATEST, AcceptanceTestBase.RUNNER_NETFX_AND_NET, + AcceptanceTestBase.LATEST_TO_LEGACY, AcceptanceTestBase.HOST_NETFX_AND_NET, + AcceptanceTestBase.LATESTSTABLE, true, true), + + // Locally-built testhost against recent shipped vstest.console versions. + CompatScenario.TestHost => ( + AcceptanceTestBase.LATEST_TO_RECENT_STABLE, AcceptanceTestBase.RUNNER_NETFX_AND_NET, + AcceptanceTestBase.LATEST, AcceptanceTestBase.HOST_NETFX_AND_NET, + AcceptanceTestBase.LATESTSTABLE, false, false), + + // Locally-built VSTestConsoleWrapper against recent shipped vstest.console versions (adds VSIX, .NET testhost only). + CompatScenario.Wrapper => ( + AcceptanceTestBase.LATEST_TO_RECENT_STABLE, AcceptanceTestBase.RUNNER_NETFX_AND_NET, + AcceptanceTestBase.LATEST, AcceptanceTestBase.HOST_NET, + AcceptanceTestBase.LATESTSTABLE, false, true), + + // Locally-built vstest.console + testhost against the range of shipped MSTest adapter versions. + CompatScenario.Adapter => ( + AcceptanceTestBase.LATEST, AcceptanceTestBase.RUNNER_NET, + AcceptanceTestBase.LATEST, AcceptanceTestBase.HOST_NETFX_AND_NET, + AcceptanceTestBase.LATESTPREVIEW_TO_LEGACY, true, true), + + _ => throw new ArgumentOutOfRangeException(nameof(scenario), scenario, null), + }; + + _builder = new CompatibilityRowsBuilder( + runnerVersions, runnerFrameworks, + hostVersions, hostFrameworks, + adapterVersions, AcceptanceTestBase.MSTEST); + + // Do not generate the data rows here, properties (e.g. DebugVSTestConsole) are not populated until after constructor is done. + } + + public bool DebugVSTestConsole { get; set; } + public bool DebugTestHost { get; set; } + public bool DebugDataCollector { get; set; } + public bool DebugStopAtEntrypoint { get; set; } + public int JustRow { get; set; } = -1; + + public string? BeforeVSTestConsoleFeature { get; set; } + public string? AfterVSTestConsoleFeature { get; set; } + + public string? BeforeTestHostFeature { get; set; } + public string? AfterTestHostFeature { get; set; } + + public string? BeforeAdapterFeature { get; set; } + public string? AfterAdapterFeature { get; set; } + + public override void CreateData(MethodInfo methodInfo) + { + _builder.WithInProcess = _withInProcess; + _builder.WithVSIXRunner = _withVsixRunner; + + _builder.BeforeRunnerFeature = BeforeVSTestConsoleFeature; + _builder.AfterRunnerFeature = AfterVSTestConsoleFeature; + + _builder.BeforeTestHostFeature = BeforeTestHostFeature; + _builder.AfterTestHostFeature = AfterTestHostFeature; + + _builder.BeforeAdapterFeature = BeforeAdapterFeature; + _builder.AfterAdapterFeature = AfterAdapterFeature; + + _builder.DebugDataCollector = DebugDataCollector; + _builder.DebugVSTestConsole = DebugVSTestConsole; + _builder.DebugTestHost = DebugTestHost; + _builder.DebugStopAtEntrypoint = DebugStopAtEntrypoint; + + _builder.JustRow = JustRow < 0 ? null : JustRow; + + var data = _builder.CreateData(); + data.ForEach(AddData); + } +} + +/// Picks which component's local changes tests for compatibility. +public enum CompatScenario +{ + /// Locally-built vstest.console against the range of shipped testhost versions (adds in-process and VSIX console rows). + VSTestConsole, + + /// Locally-built testhost against recent shipped vstest.console versions. + TestHost, + + /// Locally-built VSTestConsoleWrapper (translation layer) against recent shipped vstest.console versions (adds VSIX, .NET testhost only). + Wrapper, + + /// Locally-built vstest.console + testhost against the range of shipped MSTest adapter versions. + Adapter, +} diff --git a/test/Microsoft.TestPlatform.TestUtilities/TestMatrixAttribute.cs b/test/Microsoft.TestPlatform.TestUtilities/TestMatrixAttribute.cs new file mode 100644 index 0000000000..4ebee84d32 --- /dev/null +++ b/test/Microsoft.TestPlatform.TestUtilities/TestMatrixAttribute.cs @@ -0,0 +1,170 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Reflection; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.TestUtilities; + +/// +/// Runs the test once for every cell of the vstest.console × testhost matrix that the current OS supports, +/// using the locally-built bits. Each generated row is handed to the test as a . +/// +/// With no arguments the test runs the full matrix: both consoles — .NET Framework (vstest.console.exe) +/// and .NET (dotnet vstest.console.dll) — against both testhost target frameworks (net481 and net11.0). +/// Pin an axis to narrow it, e.g. [TestMatrix(testHost: Net)] keeps both consoles but only the +/// .NET testhost, and [TestMatrix(console: Net)] keeps both testhosts but only the .NET console. +/// +/// /InIsolation is used only for the .NET Framework console driving a .NET Framework testhost; every other +/// cell runs in its natural mode. On non-Windows the .NET Framework console and net4* testhosts are skipped. +/// +[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] +public sealed class TestMatrixAttribute : Attribute, ITestDataSource +{ + private readonly Target _console; + private readonly Target _testHost; + private readonly bool _inIsolation; + private readonly bool _inProcess; + private readonly bool _vsix; + + /// + /// Initializes a new instance of the class. + /// + /// Which vstest.console to run: both (default), only .NET Framework, or only .NET. + /// Which testhost target framework to run against: both (default), only net481, or only net11.0. + /// Emit the /InIsolation row for the .NET Framework console × .NET Framework testhost cell (default ). Ignored for every other cell. + /// Additionally run the .NET Framework console × .NET Framework testhost cell in-process (without /InIsolation). Ignored for every other cell. + /// Additively run the vstest.console shipped in the Visual Studio VSIX as its own row (a .NET Framework console and testhost), independent of the and axes. Windows-only. + public TestMatrixAttribute( + Target console = Target.Both, + Target testHost = Target.Both, + bool inIsolation = true, + bool inProcess = false, + bool vsix = false) + { + _console = console; + _testHost = testHost; + _inIsolation = inIsolation; + _inProcess = inProcess; + _vsix = vsix; + } + + public bool DebugVSTestConsole { get; set; } + public bool DebugTestHost { get; set; } + public bool DebugDataCollector { get; set; } + public bool DebugStopAtEntrypoint { get; set; } + + public IEnumerable GetData(MethodInfo methodInfo) + { + var dataRows = new List(); + var isWindows = Environment.OSVersion.Platform.ToString().StartsWith("Win"); + + var wantNetFxConsole = _console is Target.Both or Target.NetFx; + var wantNetConsole = _console is Target.Both or Target.Net; + var wantNetFxHost = _testHost is Target.Both or Target.NetFx; + var wantNetHost = _testHost is Target.Both or Target.Net; + + // .NET Framework testhost (net481) is Windows-only in its entirety. Emitted first so that for the + // both-testhost matrix the rows are ordered net481 then net11.0, matching the legacy attributes. + if (wantNetFxHost && isWindows) + { + if (wantNetConsole) + { + AddRow(dataRows, IntegrationTestBase.CoreRunnerFramework, AcceptanceTestBase.DesktopTargetFramework, inIsolationValue: null); + } + + if (wantNetFxConsole) + { + if (_inIsolation) + { + AddRow(dataRows, IntegrationTestBase.DesktopRunnerFramework, AcceptanceTestBase.DesktopTargetFramework, AcceptanceTestBase.InIsolation); + } + + if (_inProcess) + { + AddRow(dataRows, IntegrationTestBase.DesktopRunnerFramework, AcceptanceTestBase.DesktopTargetFramework, inIsolationValue: null); + } + } + } + + // The VSIX console is a .NET Framework console driving a .NET Framework testhost. `vsix: true` is + // additive: it always adds a VSIX run regardless of the console/testHost axes (Windows-only). + if (_vsix && isWindows) + { + AddRow(dataRows, IntegrationTestBase.DesktopRunnerFramework, AcceptanceTestBase.DesktopTargetFramework, inIsolationValue: null, vsixConsole: true); + } + + // .NET testhost (net11.0). + if (wantNetHost) + { + // .NET Framework console driving a .NET testhost is Windows-only; never isolated. + if (wantNetFxConsole && isWindows) + { + AddRow(dataRows, IntegrationTestBase.DesktopRunnerFramework, AcceptanceTestBase.Core11TargetFramework, inIsolationValue: null); + } + + // .NET console driving a .NET testhost runs on every OS. + if (wantNetConsole) + { + AddRow(dataRows, IntegrationTestBase.CoreRunnerFramework, AcceptanceTestBase.Core11TargetFramework, inIsolationValue: null); + } + } + + return dataRows; + } + + public string GetDisplayName(MethodInfo methodInfo, object?[]? data) + { + return string.Format(CultureInfo.CurrentCulture, "{0} ({1})", methodInfo.Name, string.Join(",", data ?? [])); + } + + private void AddRow(List dataRows, string runnerFramework, string targetFramework, string? inIsolationValue, bool vsixConsole = false) + { + var runnerInfo = new RunnerInfo + { + RunnerFramework = runnerFramework, + TargetFramework = targetFramework, + InIsolationValue = inIsolationValue, + DebugInfo = new DebugInfo + { + DebugVSTestConsole = DebugVSTestConsole, + DebugTestHost = DebugTestHost, + DebugDataCollector = DebugDataCollector, + DebugStopAtEntrypoint = DebugStopAtEntrypoint, + }, + }; + + if (vsixConsole) + { + runnerInfo.VSTestConsoleInfo = new VSTestConsoleInfo + { + Version = IntegrationTestEnvironment.LatestLocallyBuiltNugetVersion, + Path = Path.Combine(IntegrationTestEnvironment.PublishDirectory, Path.GetFileName(IntegrationTestEnvironment.LocalVsixInsertion), "vstest.console.exe"), + }; + } + + dataRows.Add([runnerInfo]); + } +} + +/// +/// Selects a runtime family — .NET Framework or .NET — for an axis of . +/// Shared by the console axis (vstest.console.exe vs dotnet vstest.console.dll) and the +/// testHost axis (net481 vs net11.0); the parameter name selects which axis it applies to. +/// +public enum Target +{ + /// Run both the .NET Framework and .NET variants of the axis (default). + Both, + + /// Run only the .NET Framework variant (vstest.console.exe / net481 testhost). + NetFx, + + /// Run only the .NET variant (dotnet vstest.console.dll / net11.0 testhost). + Net, +} From 823b6e436d7d1d592fb5f31b5db65e99a80b4745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Wed, 1 Jul 2026 17:41:19 +0200 Subject: [PATCH 08/87] [efficiency-improver] perf: eliminate Guid.ToString allocation in v2 test case serialization (#16193) * perf: eliminate ToString allocations in v2 serialization hot path In TestCaseConverterV2.Write, replace Guid.ToString() with the native Utf8JsonWriter.WriteString(string, Guid) overload, which writes the GUID directly to the UTF-8 stream without allocating an intermediate string. In TestResultConverterV2.Write, replace TimeSpan.ToString() with stackalloc + TimeSpan.TryFormat, writing via the ReadOnlySpan overload of WriteString to avoid the intermediate string allocation. Both changes produce byte-for-byte identical JSON output (D-format GUID and c-format TimeSpan), so the wire protocol is unchanged. The improvement eliminates two heap allocations per serialized test result on the IPC write path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * revert small change --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Azat Mukhametshin --- .../Serialization/TestCaseConverterV2.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestCaseConverterV2.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestCaseConverterV2.cs index 17189ba37b..3313b4405e 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestCaseConverterV2.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestCaseConverterV2.cs @@ -76,7 +76,7 @@ internal class TestCaseConverterV2 : JsonConverter public override void Write(Utf8JsonWriter writer, TestCase value, JsonSerializerOptions options) { writer.WriteStartObject(); - writer.WriteString("Id", value.Id.ToString()); + writer.WriteString("Id", value.Id); writer.WriteString("FullyQualifiedName", value.FullyQualifiedName); writer.WriteString("DisplayName", value.DisplayName); writer.WriteString("ExecutorUri", value.ExecutorUri?.OriginalString); From c47021c603bfdc028e01e154813e73bb323f7f7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Thu, 2 Jul 2026 17:12:01 +0200 Subject: [PATCH 09/87] Simplify parallel run/discovery to a plain work queue (#16199) * Simplify parallel run/discovery to a plain work queue ParallelOperationManager was a work queue pretending to be something more complicated. It kept a fixed Slot[] with eight mutable flags per slot and re-scanned it through three interleaved loops on every pump. Almost all of that machinery existed to support pre-starting testhosts (VSTEST_HOSTPRESTART_COUNT), which has been disabled by default for a long time and only ever ran for non-parallel runs. Rip out the pre-start feature and reduce the manager to what it always was underneath: a Queue of workloads and a List of active managers. Up to MaxParallelLevel managers run at once, and when one finishes it calls RunNextWork to free its slot and pull the next workload until the queue is empty. OccupiedSlotCount/AvailableSlotCount stay for the tests to observe. Drop the initializeWorkload callback and the (bool initialized, Task? initTask) plumbing from the run and discovery managers. Both now just Initialize -> Initialize(TestRun|Discovery) -> (StartTestRun|DiscoverTests) inline, which is exactly the non-pre-started path from before, so observable behaviour does not change. RunNextWork also loses its bool return that no caller used. Note: this removes VSTEST_HOSTPRESTART_COUNT and its documentation. The ProxyTestSessionManager pre-start is a separate feature and is left untouched. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Drop the vestigial slot counters, document why the queue stays custom OccupiedSlotCount and AvailableSlotCount were cached fields that UpdateSlotCounts kept in sync on every transition, plus an ordering rule that they had to be refreshed before the lock was released. But the scheduler already decides off _activeManagers.Count directly, and nothing in production reads the counts - only the unit tests do. So they are now computed getters over _activeManagers, and UpdateSlotCounts and its four call sites are gone. Also added an ASCII diagram and a note on the class explaining why this stays a hand-rolled queue instead of a SemaphoreSlim / Channel / Dataflow block: a workload completes through an out-of-band IPC event (RunNextWork), not an awaitable Task, so a bounded primitive would just move the complexity into a TaskCompletionSource bridge and still need the active-manager list for broadcast cancellation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/environment-variables.md | 14 - .../Parallel/ParallelOperationManager.cs | 414 ++++++------------ .../Parallel/ParallelProxyDiscoveryManager.cs | 55 +-- .../Parallel/ParallelProxyExecutionManager.cs | 70 +-- .../Parallel/ParallelOperationManagerTests.cs | 33 +- 5 files changed, 174 insertions(+), 412 deletions(-) diff --git a/docs/environment-variables.md b/docs/environment-variables.md index cbe53ba821..0809ac93f2 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -212,13 +212,6 @@ This document lists all environment variables that are understood and handled by - **Description**: Specifies the directory where telemetry log files should be written. - **Example**: `VSTEST_LOGTELEMETRY_PATH=C:\TelemetryLogs` -## Performance and Parallelization Variables - -### VSTEST_HOSTPRESTART_COUNT -- **Description**: Sets the number of testhosts to pre-start for improved performance in parallel test execution. -- **Format**: Integer value -- **Example**: `VSTEST_HOSTPRESTART_COUNT=4` - ## Configuration and Path Variables ### VSTEST_CONSOLE_PATH @@ -283,13 +276,6 @@ set VSTEST_DIAG_VERBOSITY=Verbose dotnet test MyTests.dll ``` -### Performance Optimization -```bash -# Pre-start testhosts for better parallel performance -set VSTEST_HOSTPRESTART_COUNT=4 -dotnet test MyTests.dll --parallel -``` - ### Crash Dump Collection ```bash # Configure crash dump collection diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelOperationManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelOperationManager.cs index 9003a84790..6c2f7b158c 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelOperationManager.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelOperationManager.cs @@ -3,47 +3,78 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; using System.Threading.Tasks; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.Parallel; using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; /// -/// Manages work that is done on multiple managers (testhosts) in parallel such as parallel discovery or parallel run. +/// Manages work that is done on multiple managers (testhosts) in parallel, such as parallel discovery or parallel +/// run. It is a plain producer/consumer queue: enqueues all the workloads (a single source, +/// multiple sources, or a batch of test cases), and up to managers pull from that +/// queue. When a manager finishes its workload the consumer calls , which frees the manager +/// and pulls the next workload until the queue is empty. /// +/// +/// +/// workloads +/// | StartWork enqueues every workload +/// v +/// +-------------------+ +/// | _pendingWorkloads | FIFO queue of work waiting for a free manager +/// +-------------------+ +/// | RunWorkInParallel dequeues while a slot is free +/// v (i.e. while _activeManagers holds fewer than MaxParallelLevel managers) +/// +-------+ +-------+ +-------+ +/// | mgr 1 | | mgr 2 | | mgr 3 | _activeManagers: at most MaxParallelLevel testhosts +/// +-------+ +-------+ +-------+ +/// | | | +/// | | | each manager runs one workload on its testhost over IPC; +/// v v v from here on the work is fire-and-forget +/// - - - - - asynchronous - - - - - +/// | | | when a testhost is done, its event handler calls +/// v v v RunNextWork(manager) - that out-of-band call is the "done" signal +/// RunNextWork: remove the finished manager, then RunWorkInParallel pulls the next workload +/// +/// +/// Why is this hand-rolled instead of a bounded primitive (SemaphoreSlim, System.Threading.Channels, or TPL +/// Dataflow's ActionBlock with MaxDegreeOfParallelism)? All of those bound concurrency around an awaitable unit of +/// work: the primitive starts a Task and watches that Task complete to release a slot. Our unit of work is not +/// awaitable. A manager is a separate testhost process; it runs its workload over an IPC connection and reports +/// completion much later through an out-of-band event (HandlePartialRunComplete / HandlePartialDiscoveryComplete), +/// which is what calls RunNextWork. To adopt a bounded primitive we would have to bridge every workload to a +/// TaskCompletionSource, complete it from that event, and add the matching cancellation plumbing - which relocates +/// the complexity instead of removing it, and we would still need the queue and the active-manager list anyway. +/// +/// We also keep the full active-manager list (not just a counter) because it is the cancellation surface: Abort / +/// Cancel / Close are broadcast to every in-flight manager through , so we need +/// the live set of managers, not only how many there are. +/// internal sealed class ParallelOperationManager : IDisposable { - private const int PreStart = 0; - private readonly static int VSTEST_HOSTPRESTART_COUNT = - int.TryParse( - Environment.GetEnvironmentVariable(nameof(VSTEST_HOSTPRESTART_COUNT)), - out int num) - ? num - : PreStart; private readonly Func _createNewManager; + private readonly object _lock = new(); + + // Workloads that are waiting to be picked up by a manager. + private readonly Queue> _pendingWorkloads = new(); + + // Managers that are currently processing a workload. There are never more than MaxParallelLevel of them. + private readonly List _activeManagers = new(); - /// - /// Default number of Processes - /// private TEventHandler? _eventHandler; private Func? _getEventHandler; - private Func? _initializeWorkload; - private Action? _runWorkload; - private bool _acceptMoreWork; - private readonly List> _workloads = new(); - private readonly List _managerSlots = new(); - - private readonly object _lock = new(); + private Action? _runWorkload; + private bool _acceptMoreWork = true; public int MaxParallelLevel { get; } - public int OccupiedSlotCount { get; private set; } - public int AvailableSlotCount { get; private set; } - public int PreStartCount { get; private set; } + + /// Number of managers that are currently running a workload. Exposed mainly for tests and diagnostics. + public int OccupiedSlotCount => _activeManagers.Count; + + /// Number of managers that could still be started before reaching . Exposed mainly for tests and diagnostics. + public int AvailableSlotCount => MaxParallelLevel - _activeManagers.Count; /// /// Creates new instance of ParallelOperationManager. @@ -55,67 +86,38 @@ public ParallelOperationManager(Func new Slot { Index = i })); - SetOccupiedSlotCount(); - } - } - - private void SetOccupiedSlotCount() - { - AvailableSlotCount = _managerSlots.Count(s => !s.HasWork); - OccupiedSlotCount = _managerSlots.Count - AvailableSlotCount; - - if (EqtTrace.IsVerboseEnabled) - { - EqtTrace.Verbose($"ParallelOperationManager.SetOccupiedSlotCount: Setting slot counts AvailableSlotCount = {AvailableSlotCount}, OccupiedSlotCount = {OccupiedSlotCount}."); - EqtTrace.Verbose($"Occupied slots:\n{(string.Join("\n", _managerSlots.Where(s => s.HasWork).Select((slot) => $"{slot.Index}: {GetSourcesForSlotExpensive(slot)}").ToArray()))}"); - - } } public void StartWork( List> workloads, TEventHandler eventHandler, Func getEventHandler, - Func initializeWorkload, - Action runWorkload) + Action runWorkload) { _ = workloads ?? throw new ArgumentNullException(nameof(workloads)); _eventHandler = eventHandler ?? throw new ArgumentNullException(nameof(eventHandler)); _getEventHandler = getEventHandler ?? throw new ArgumentNullException(nameof(getEventHandler)); - _initializeWorkload = initializeWorkload ?? throw new ArgumentNullException(nameof(initializeWorkload)); _runWorkload = runWorkload ?? throw new ArgumentNullException(nameof(runWorkload)); - EqtTrace.Verbose($"ParallelOperationManager.StartWork: Starting adding {workloads.Count} workloads."); - _workloads.AddRange(workloads); + EqtTrace.Verbose($"ParallelOperationManager.StartWork: Enqueuing {workloads.Count} workloads."); + + lock (_lock) + { + _acceptMoreWork = true; + foreach (var workload in workloads) + { + _pendingWorkloads.Enqueue(workload); + } + } - ClearSlots(acceptMoreWork: true); RunWorkInParallel(); } - // This does not do anything in parallel, all the workloads we schedule are offloaded to separate Task in the _runWorkload callback. - // I did not want to change that, yet but this is the correct place to do that offloading. Not each manager. - private bool RunWorkInParallel() + /// + /// Starts as many pending workloads as there are free manager slots (up to ). + /// + private void RunWorkInParallel() { - // TODO: Right now we don't re-use shared hosts, but if we did, this is the place - // where we should find a workload that fits the manager if any of them is shared. - // Or tear it down, and start a new one. - if (_eventHandler == null) throw new InvalidOperationException($"{nameof(_eventHandler)} was not provided."); @@ -125,225 +127,115 @@ private bool RunWorkInParallel() if (_runWorkload == null) throw new InvalidOperationException($"{nameof(_runWorkload)} was not provided."); - // Reserve slots and assign them work under the lock so we keep the slots consistent. - Slot[] slots; + // Reserve the managers under the lock so the slots stay consistent, but start their work outside of the + // lock. That way, if multiple completions come in at the same time, they only block each other while + // reserving a slot, and not while actually starting their assigned work. + List<(TManager Manager, TEventHandler EventHandler, TWorkload Work)> reserved = new(); + int activeCount; + int pendingCount; lock (_lock) { - // When HandlePartialDiscovery or HandlePartialRun are in progress, and we call StopAllManagers, - // it is possible that we will clear all slots, and have RunWorkInParallel waiting on the lock, - // so when it is allowed to enter it will try to add more work, but we already cancelled, - // so we should not start more work. + // When HandlePartialDiscovery or HandlePartialRun are in progress and we call StopAllManagers, it is + // possible that RunWorkInParallel is waiting on the lock. When it is finally allowed in, it should not + // start any more work, because we already cancelled. if (!_acceptMoreWork) { - EqtTrace.Verbose($"ParallelOperationManager.RunWorkInParallel: We don't accept more work, returning false."); - return false; + EqtTrace.Verbose("ParallelOperationManager.RunWorkInParallel: We don't accept more work, doing nothing."); + return; } - // We grab all empty slots. - var availableSlots = _managerSlots.Where(slot => !slot.HasWork).ToImmutableArray(); - var occupiedSlots = MaxParallelLevel - (availableSlots.Length - PreStartCount); - // We grab all available workloads. - var availableWorkloads = _workloads.Where(workload => workload != null).ToImmutableArray(); - // We take the amount of workloads to fill all the slots, or just as many workloads - // as there are if there are less workloads than slots. - var amount = Math.Min(availableSlots.Length, availableWorkloads.Length); - var workloadsToAdd = availableWorkloads.Take(amount).ToImmutableArray(); - - // We associate each workload to a slot, if we reached the max parallel - // level, then we will run only initialize step of the given workload. - for (int i = 0; i < amount; i++) + while (_activeManagers.Count < MaxParallelLevel && _pendingWorkloads.Count > 0) { - var slot = availableSlots[i]; - var workload = workloadsToAdd[i]; - slot.ShouldPreStart = occupiedSlots + i + 1 > MaxParallelLevel; - + var workload = _pendingWorkloads.Dequeue(); var manager = _createNewManager(workload.Provider, workload.Work); var eventHandler = _getEventHandler(_eventHandler, manager); - slot.EventHandler = eventHandler; - slot.Manager = manager; - slot.ManagerInfo = workload.Provider; - slot.Work = workload.Work; - - _workloads.Remove(workload); - - EqtTrace.Verbose($"ParallelOperationManager.RunWorkInParallel: Adding 1 workload to slot, remaining workloads {_workloads.Count}."); - - // This must be set last, every loop below looks at this property, - // and they can do so from a different thread. So if we mark it as HasWork before actually assigning the work - // we can pick up the slot, but it has no associated work yet. - slot.HasWork = true; + _activeManagers.Add(manager); + reserved.Add((manager, eventHandler, workload.Work)); } - slots = _managerSlots.ToArray(); - SetOccupiedSlotCount(); + activeCount = _activeManagers.Count; + pendingCount = _pendingWorkloads.Count; } - // Kick of the work in parallel outside of the lock so if we have more requests to run - // that come in at the same time we only block them from reserving the same slot at the same time - // but not from starting their assigned work at the same time. - - // Kick of all pre-started hosts from the ones that had the longest time to initialize. - // - // This code should be safe even outside the lock since HasWork is only changed when work is - // complete and only for the slot that completed work. It is not possible to complete work before - // starting it (which is what we are trying to do here). - var startedWork = 0; - foreach (var slot in slots.Where(s => s.HasWork && !s.IsRunning && s.IsPreStarted).OrderBy(s => s.PreStartTime)) + if (EqtTrace.IsVerboseEnabled) { - startedWork++; - slot.IsRunning = true; - if (EqtTrace.IsVerboseEnabled) - { - EqtTrace.Verbose($"ParallelOperationManager.RunWorkInParallel: Running on pre-started host for work (source) {GetSourcesForSlotExpensive(slot)}: {(DateTime.Now.TimeOfDay - slot.PreStartTime).TotalMilliseconds}ms {slot.InitTask?.Status}"); - } - _runWorkload(slot.Manager!, slot.EventHandler!, slot.Work!, slot.IsPreStarted, slot.InitTask); - - // We already started as many as we were allowed, jump out; - if (startedWork == MaxParallelLevel) - { - EqtTrace.Verbose($"ParallelOperationManager.RunWorkInParallel: We started {startedWork} work items, which is the max parallel level. Won't start more work."); - break; - } + EqtTrace.Verbose($"ParallelOperationManager.RunWorkInParallel: {activeCount} managers active (max {MaxParallelLevel}), {pendingCount} workloads still pending."); } - // We already started as many pre-started testhosts as we are allowed by the max parallel level - // skip running more work. - if (startedWork < MaxParallelLevel) + foreach (var (manager, eventHandler, work) in reserved) { - foreach (var slot in slots) - { - if (slot.HasWork && !slot.IsRunning) - { - if (!slot.ShouldPreStart) - { - startedWork++; - slot.IsRunning = true; - if (EqtTrace.IsVerboseEnabled) - { - EqtTrace.Verbose($"ParallelOperationManager.RunWorkInParallel: Started host in slot number {slot.Index} for work (source): {GetSourcesForSlotExpensive(slot)}."); - } - _runWorkload(slot.Manager!, slot.EventHandler!, slot.Work!, slot.IsPreStarted, slot.InitTask); - } - } - - // We already started as many as we were allowed, jump out; - if (startedWork == MaxParallelLevel) - { - EqtTrace.Verbose($"ParallelOperationManager.RunWorkInParallel: We started {startedWork} work items, which is the max parallel level. Won't start more work."); - break; - } - } + EqtTrace.Verbose("ParallelOperationManager.RunWorkInParallel: Starting work on a manager."); + _runWorkload(manager, eventHandler, work); } - - var preStartedWork = 0; - foreach (var slot in slots) - { - if (slot.HasWork && slot.ShouldPreStart && !slot.IsPreStarted) - { - preStartedWork++; - slot.PreStartTime = DateTime.Now.TimeOfDay; - slot.IsPreStarted = true; - if (EqtTrace.IsVerboseEnabled) - { - EqtTrace.Verbose($"ParallelOperationManager.RunWorkInParallel: Pre-starting a host for work (source): {GetSourcesForSlotExpensive(slot)}."); - } - slot.InitTask = _initializeWorkload!(slot.Manager!, slot.EventHandler!, slot.Work!); - } - } - - // Return true when we started more work. Or false, when there was nothing more to do. - // This will propagate to handling of partial discovery or partial run. - var weAddedMoreWork = preStartedWork + startedWork > 0; - EqtTrace.Verbose($"ParallelOperationManager.RunWorkInParallel: We started {preStartedWork + startedWork} work items in here, returning {weAddedMoreWork}."); - return weAddedMoreWork; } - public bool RunNextWork(TManager completedManager) + /// + /// Frees the manager that just completed its workload and starts the next pending workload if there is any. + /// + public void RunNextWork(TManager completedManager) { ValidateArg.NotNull(completedManager, nameof(completedManager)); - ClearCompletedSlot(completedManager); - return RunWorkInParallel(); - } - private void ClearCompletedSlot(TManager completedManager) - { lock (_lock) { - var completedSlot = _managerSlots.Where(s => ReferenceEquals(completedManager, s.Manager)).ToImmutableArray(); - // When HandlePartialDiscovery or HandlePartialRun are in progress, and we call StopAllManagers, - // it is possible that we will clear all slots, while ClearCompletedSlot is waiting on the lock, - // so when it is allowed to enter it will fail to find the respective slot and fail. In this case it is - // okay that the slot is not found, and we do nothing, because we already stopped all work and cleared the slots. - if (completedSlot.Length == 0) - { - if (_acceptMoreWork) - { - throw new InvalidOperationException("The provided manager was not found in any slot."); - } - else - { - return; - } - } + var removed = TryRemoveActiveManager(completedManager); - if (completedSlot.Length > 1) + // When HandlePartialDiscovery or HandlePartialRun are in progress and we call StopAllManagers, it is + // possible that we already cleared all managers while RunNextWork was waiting on the lock. In that case + // it is okay that the manager is not found, because we already stopped all work. + if (!removed && _acceptMoreWork) { - throw new InvalidOperationException("The provided manager was found in multiple slots."); + throw new InvalidOperationException("The provided manager was not found among the active managers."); } + } - if (EqtTrace.IsVerboseEnabled) + RunWorkInParallel(); + } + + // Called under _lock. Uses reference equality because each workload gets its own freshly created manager. + private bool TryRemoveActiveManager(TManager manager) + { + for (int i = 0; i < _activeManagers.Count; i++) + { + if (ReferenceEquals(_activeManagers[i], manager)) { - EqtTrace.Verbose($"ParallelOperationManager.ClearCompletedSlot: Clearing slot number {completedSlot[0].Index} with work (source): {GetSourcesForSlotExpensive(completedSlot[0])}."); + _activeManagers.RemoveAt(i); + return true; } - var slot = completedSlot[0]; - slot.PreStartTime = TimeSpan.Zero; - slot.Work = default(TWorkload); - slot.HasWork = false; - slot.ShouldPreStart = false; - slot.IsPreStarted = false; - slot.InitTask = null; - slot.IsRunning = false; - slot.Manager = default(TManager); - slot.EventHandler = default(TEventHandler); - - SetOccupiedSlotCount(); } - } - private static string GetSourcesForSlotExpensive(ParallelOperationManager.Slot slot) - { - return string.Join(", ", (slot.Work as DiscoveryCriteria)?.Sources ?? (slot.Work as TestRunCriteria)?.Sources ?? []); + return false; } public void DoActionOnAllManagers(Action action, bool doActionsInParallel = false) { - EqtTrace.Verbose($"ParallelOperationManager.DoActionOnAllManagers: Calling an action on all managers."); - // We don't need to lock here, we just grab the current list of - // slots that are occupied (have managers) and run action on each one of them. - var managers = _managerSlots.Where(slot => slot.HasWork).Select(slot => slot.Manager).ToImmutableArray(); - int i = 0; - var actionTasks = new Task[managers.Length]; - foreach (var manager in managers) + EqtTrace.Verbose("ParallelOperationManager.DoActionOnAllManagers: Calling an action on all managers."); + + TManager[] managers; + lock (_lock) { - if (manager == null) - continue; + managers = _activeManagers.ToArray(); + } - // Read the array before firing the task - beware of closures - if (doActionsInParallel) - { - actionTasks[i] = Task.Run(() => action(manager)); - i++; - } - else + if (!doActionsInParallel) + { + foreach (var manager in managers) { DoManagerAction(() => action(manager)); } + + return; } - if (doActionsInParallel) + var actionTasks = new Task[managers.Length]; + for (int i = 0; i < managers.Length; i++) { - DoManagerAction(() => Task.WaitAll(actionTasks)); + // Read the array before firing the task - beware of closures. + var manager = managers[i]; + actionTasks[i] = Task.Run(() => action(manager)); } + + DoManagerAction(() => Task.WaitAll(actionTasks)); } private static void DoManagerAction(Action action) @@ -363,40 +255,18 @@ private static void DoManagerAction(Action action) internal void StopAllManagers() { - EqtTrace.Verbose($"ParallelOperationManager.StopAllManagers: Stopping all managers."); - ClearSlots(acceptMoreWork: false); + EqtTrace.Verbose("ParallelOperationManager.StopAllManagers: Stopping all managers and discarding pending workloads."); + lock (_lock) + { + _acceptMoreWork = false; + _pendingWorkloads.Clear(); + _activeManagers.Clear(); + } } public void Dispose() { - EqtTrace.Verbose($"ParallelOperationManager.Dispose: Disposing all managers."); - ClearSlots(acceptMoreWork: false); - } - - private class Slot - { - public int Index { get; set; } - public bool HasWork { get; set; } - - public bool ShouldPreStart { get; set; } - - public Task? InitTask { get; set; } - - public bool IsRunning { get; set; } - - public TManager? Manager { get; set; } - - public TestRuntimeProviderInfo? ManagerInfo { get; set; } - - public TEventHandler? EventHandler { get; set; } - - public TWorkload? Work { get; set; } - public bool IsPreStarted { get; internal set; } - public TimeSpan PreStartTime { get; internal set; } - - public override string ToString() - { - return $"{Index}: HasWork: {HasWork}, ShouldPreStart: {ShouldPreStart}, IsPreStarted: {IsPreStarted}, IsRunning: {IsRunning}"; - } + EqtTrace.Verbose("ParallelOperationManager.Dispose: Disposing all managers."); + StopAllManagers(); } } diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyDiscoveryManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyDiscoveryManager.cs index 417b619585..d0ea1f128a 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyDiscoveryManager.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyDiscoveryManager.cs @@ -109,7 +109,7 @@ public void DiscoverTests(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEve // _dataAggregator.MarkAsAborted(); } - _parallelOperationManager.StartWork(runnableWorkloads, eventHandler, GetParallelEventHandler, InitializeDiscoverTestsOnConcurrentManager, DiscoverTestsOnConcurrentManager); + _parallelOperationManager.StartWork(runnableWorkloads, eventHandler, GetParallelEventHandler, DiscoverTestsOnConcurrentManager); } private ITestDiscoveryEventsHandler2 GetParallelEventHandler(ITestDiscoveryEventsHandler2 eventHandler, IProxyDiscoveryManager concurrentManager) @@ -257,41 +257,16 @@ static DiscoveryCriteria NewDiscoveryCriteriaFromSourceAndSettings(IEnumerable - /// Triggers the discovery for the next data object on the concurrent discoverer - /// Each concurrent discoverer calls this method, once its completed working on previous data + /// Triggers the discovery for the next data object on the concurrent discoverer. + /// Each concurrent discoverer calls this method once it has completed working on the previous data. /// /// Proxy discovery manager instance. /// Discovery events handler. - /// Discovery criteria a parameters. - private Task InitializeDiscoverTestsOnConcurrentManager(IProxyDiscoveryManager proxyDiscoveryManager, ITestDiscoveryEventsHandler2 eventHandler, DiscoveryCriteria discoveryCriteria) - { - // Kick off another discovery task for the next source - return Task.Run(() => - { - EqtTrace.Verbose("ProxyParallelDiscoveryManager.InitializeDiscoverTestsOnConcurrentManager: Discovery preparation started."); - - proxyDiscoveryManager.Initialize(_skipDefaultAdapters); - proxyDiscoveryManager.InitializeDiscovery(discoveryCriteria, eventHandler, _skipDefaultAdapters); - - EqtTrace.Verbose($"ProxyParallelDiscoveryManager.InitializeDiscoverTestsOnConcurrentManager: Init only: {string.Join(", ", discoveryCriteria.Sources)}"); - }); - } - - /// - /// Triggers the discovery for the next data object on the concurrent discoverer - /// Each concurrent discoverer calls this method, once its completed working on previous data - /// - /// - /// - /// - /// - /// + /// Discovery criteria and parameters. private void DiscoverTestsOnConcurrentManager( IProxyDiscoveryManager proxyDiscoveryManager, ITestDiscoveryEventsHandler2 eventHandler, - DiscoveryCriteria discoveryCriteria, - bool initialized, - Task? task) + DiscoveryCriteria discoveryCriteria) { // If we do the scheduling incorrectly this will get null. It should not happen, but it has happened before. if (discoveryCriteria == null) @@ -299,26 +274,14 @@ private void DiscoverTestsOnConcurrentManager( throw new ArgumentNullException(nameof(discoveryCriteria)); } - // Kick off another discovery task for the next source + // Kick off the discovery task for the next source. Task.Run(() => { - EqtTrace.Verbose("ParallelProxyDiscoveryManager: Discovery started."); - if (!initialized) - { - EqtTrace.Verbose($"ProxyParallelDiscoveryManager.DiscoverTestsOnConcurrentManager: Initialize right before run: {string.Join(", ", discoveryCriteria.Sources)}"); - proxyDiscoveryManager.Initialize(_skipDefaultAdapters); - proxyDiscoveryManager.InitializeDiscovery(discoveryCriteria, eventHandler, _skipDefaultAdapters); - } - else - { - task?.Wait(); - } - - EqtTrace.Verbose($"ProxyParallelDiscoveryManager.DiscoverTestsOnConcurrentManager: Run: {string.Join(", ", discoveryCriteria.Sources)}"); + EqtTrace.Verbose($"ParallelProxyDiscoveryManager.DiscoverTestsOnConcurrentManager: Discovery started for: {string.Join(", ", discoveryCriteria.Sources)}"); + proxyDiscoveryManager.Initialize(_skipDefaultAdapters); + proxyDiscoveryManager.InitializeDiscovery(discoveryCriteria, eventHandler, _skipDefaultAdapters); proxyDiscoveryManager.DiscoverTests(discoveryCriteria, eventHandler); }).ContinueWith(t => HandleError(eventHandler, t), TaskContinuationOptions.OnlyOnFaulted); - - EqtTrace.Verbose("ProxyParallelDiscoveryManager.DiscoverTestsOnConcurrentManager: No sources available for discovery."); } private void HandleError(ITestDiscoveryEventsHandler2 eventHandler, Task t) diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs index c020d44d85..7f21f50838 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs @@ -115,7 +115,7 @@ public int StartTestRun(TestRunCriteria testRunCriteria, IInternalTestRunEventsH // _currentRunDataAggregator.MarkAsAborted(); } - _parallelOperationManager.StartWork(runnableWorkloads, eventHandler, GetParallelEventHandler, PrepareTestRunOnConcurrentManager, StartTestRunOnConcurrentManager); + _parallelOperationManager.StartWork(runnableWorkloads, eventHandler, GetParallelEventHandler, StartTestRunOnConcurrentManager); // Why 1? Because this is supposed to be a processId, and that is just the default that was chosen by someone before me, // and maybe is checked somewhere, but I don't see it checked in our codebase. @@ -173,21 +173,8 @@ public bool HandlePartialRunComplete( // and queue another test run. if (!testRunCompleteArgs.IsCanceled && !_abortRequested) { - // Do NOT return true here, there should be only one place where this method returns true, - // and cancellation or success or any other other combination or timing should result in only one true. - // This is largely achieved by returning true above when "allRunsCompleted" is true. That variable is true - // when we cancel all sources or when we complete all sources. - // - // But we can also start a source, and cancel right after, which will remove all managers, and RunNextWork returns - // false, because we had no more work to do. If we check that result here and return true, then the whole logic is - // broken and we end up calling RunComplete handlers twice and writing logger output to screen twice. So don't do it. - // var hadMoreWork = _parallelOperationManager.RunNextWork(proxyExecutionManager); - // if (!hadMoreWork) - // { - // return true; - // } EqtTrace.Verbose("ParallelProxyExecutionManager: HandlePartialRunComplete: Not cancelled or aborted, running next work."); - var _ = _parallelOperationManager.RunNextWork(proxyExecutionManager); + _parallelOperationManager.RunNextWork(proxyExecutionManager); } else { @@ -394,37 +381,14 @@ private ParallelRunEventsHandler GetParallelEventHandler(IInternalTestRunEventsH _currentRunDataAggregator); } - private Task PrepareTestRunOnConcurrentManager(IProxyExecutionManager proxyExecutionManager, IInternalTestRunEventsHandler eventHandler, TestRunCriteria testRunCriteria) - { - return Task.Run(() => - { - if (!proxyExecutionManager.IsInitialized) - { - proxyExecutionManager.Initialize(_skipDefaultAdapters); - } - - // NOTE: No need to increment the number of started clients on initialization since the - // client doesn't really count as started unless some work is done on it. Incrementing - // the number of clients will result in failing acceptance tests because they expect all - // clients to be done running their workloads when aborting/cancelling and that doesn't - // happen with an initialized workload that is never run. - // - // Interlocked.Increment(ref _runStartedClients); <- BUG: Is this a bug waiting to happen for pre-started hosts? - proxyExecutionManager.InitializeTestRun(testRunCriteria, eventHandler); - }); - } - /// - /// Triggers the execution for the next data object on the concurrent executor - /// Each concurrent executor calls this method, once its completed working on previous data + /// Triggers the execution for the next data object on the concurrent executor. + /// Each concurrent executor calls this method once it has completed working on the previous data. /// - /// True, if execution triggered private void StartTestRunOnConcurrentManager( IProxyExecutionManager proxyExecutionManager, IInternalTestRunEventsHandler eventHandler, - TestRunCriteria testRunCriteria, - bool initialized, - Task? initTask) + TestRunCriteria testRunCriteria) { // If we do the scheduling incorrectly this will get null. It should not happen, but it has happened before. if (testRunCriteria == null) @@ -434,27 +398,17 @@ private void StartTestRunOnConcurrentManager( Task.Run(() => { - if (!initialized) + if (!proxyExecutionManager.IsInitialized) { - if (!proxyExecutionManager.IsInitialized) - { - EqtTrace.Verbose("ParallelProxyExecutionManager.StartTestRunOnConcurrentManager: Initializing uninitialized client. Started clients: " + _runStartedClients); - proxyExecutionManager.Initialize(_skipDefaultAdapters); - } - - EqtTrace.Verbose("ParallelProxyExecutionManager.StartTestRunOnConcurrentManager: Initializing test run. Started clients: " + _runStartedClients); - Interlocked.Increment(ref _runStartedClients); - proxyExecutionManager.InitializeTestRun(testRunCriteria, eventHandler); - } - else - { - EqtTrace.Verbose("ParallelProxyExecutionManager.StartTestRunOnConcurrentManager: Waiting for pre-initialized client to finish initialization. Started clients: " + _runStartedClients); - initTask!.Wait(); - EqtTrace.Verbose("ParallelProxyExecutionManager.StartTestRunOnConcurrentManager: Pre-initialized client finished initialization. Started clients: " + _runStartedClients); + EqtTrace.Verbose("ParallelProxyExecutionManager.StartTestRunOnConcurrentManager: Initializing uninitialized client. Started clients: " + _runStartedClients); + proxyExecutionManager.Initialize(_skipDefaultAdapters); } - EqtTrace.Verbose("ParallelProxyExecutionManager.StartTestRunOnConcurrentManager: Execution starting. Started clients: " + _runStartedClients); + Interlocked.Increment(ref _runStartedClients); + EqtTrace.Verbose("ParallelProxyExecutionManager.StartTestRunOnConcurrentManager: Initializing test run. Started clients: " + _runStartedClients); + proxyExecutionManager.InitializeTestRun(testRunCriteria, eventHandler); + EqtTrace.Verbose("ParallelProxyExecutionManager.StartTestRunOnConcurrentManager: Execution starting. Started clients: " + _runStartedClients); proxyExecutionManager.StartTestRun(testRunCriteria, eventHandler); EqtTrace.Verbose("ParallelProxyExecutionManager.StartTestRunOnConcurrentManager: Execution started. Started clients: " + _runStartedClients); }) diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelOperationManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelOperationManagerTests.cs index 039bcbb479..e99d776846 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelOperationManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/Parallel/ParallelOperationManagerTests.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; @@ -36,14 +35,14 @@ public void OperationManagerShouldRunOnlyMaximumParallelLevelOfWorkInParallelEve List workerCounts = new(); Func getEventHandler = (handler, _) => handler; - Action runWorkload = (manager, _, _, _, _) => + Action runWorkload = (manager, _, _) => { // Every time we run a workload check how many slots are occupied, // we should see 3 slots at max, because that is our max parallel level, we should NOT see 4 or more: // This is what the data should be: // - At the start we schedule as much work as we can, workloads 1, 2, 3 // are started and grab a slot. - // We only update the slot count after scheduling all the work up to the max parallel level, + // All managers are reserved (added to the active set) before any workload starts running, // so when we reach this method, all the slots are already occupied, so for workloads 1, 2, 3 we record 3, 3, 3. // - Workload 1 finishes and leaves the slot, 4 starts and grabs a slot, 2, 3, 4 are now running we record 3. // - workload 2 finishes and leaves the slot, 5 starts and grabs a slot, 3, 4, 5 are now running we record 3. @@ -61,11 +60,9 @@ public void OperationManagerShouldRunOnlyMaximumParallelLevelOfWorkInParallelEve // and pass on the current manager that is done. parallelOperationManager.RunNextWork(manager); }; - Func initializeWorkload = (_, _, _) => - Task.Run(() => System.Threading.Thread.Sleep(100)); // Act - parallelOperationManager.StartWork(workloads, eventHandler, getEventHandler, initializeWorkload, runWorkload); + parallelOperationManager.StartWork(workloads, eventHandler, getEventHandler, runWorkload); // Assert workerCounts.Should().BeEquivalentTo(new[] { 3, 3, 3, 2, 1 }); @@ -88,7 +85,7 @@ public void OperationManagerShouldCreateOnlyAsManyParallelWorkersAsThereAreWorkl List workerCounts = new(); Func getEventHandler = (handler, _) => handler; - Action runWorkload = (manager, _, _, _, _) => + Action runWorkload = (manager, _, _) => { // See comments in test above for explanation. workerCounts.Add(parallelOperationManager.OccupiedSlotCount); @@ -96,11 +93,9 @@ public void OperationManagerShouldCreateOnlyAsManyParallelWorkersAsThereAreWorkl parallelOperationManager.RunNextWork(manager); }; - Func initializeWorkload = (_, _, _) => - Task.Run(() => System.Threading.Thread.Sleep(100)); // Act - parallelOperationManager.StartWork(workloads, eventHandler, getEventHandler, initializeWorkload, runWorkload); + parallelOperationManager.StartWork(workloads, eventHandler, getEventHandler, runWorkload); // Assert workerCounts.Should().BeEquivalentTo(new[] { 2, 1 }); @@ -125,7 +120,7 @@ public void OperationManagerShouldCreateAsManyMaxParallelLevel() List availableWorkerCounts = new(); Func getEventHandler = (handler, _) => handler; - Action runWorkload = (manager, _, _, _, _) => + Action runWorkload = (manager, _, _) => { // See comments in test above for explanation. workerCounts.Add(parallelOperationManager.OccupiedSlotCount); @@ -134,11 +129,9 @@ public void OperationManagerShouldCreateAsManyMaxParallelLevel() parallelOperationManager.RunNextWork(manager); }; - Func initializeWorkload = (_, _, _) => - Task.Run(() => System.Threading.Thread.Sleep(100)); // Act - parallelOperationManager.StartWork(workloads, eventHandler, getEventHandler, initializeWorkload, runWorkload); + parallelOperationManager.StartWork(workloads, eventHandler, getEventHandler, runWorkload); // Assert workerCounts.Should().BeEquivalentTo(new[] { 2, 1 }); @@ -165,7 +158,7 @@ public void OperationManagerMovesToTheNextWorkloadOnlyWhenRunNextWorkIsCalled() List workloadsProcessed = new(); Func getEventHandler = (handler, _) => handler; - Action runWorkload = (manager, _, workload, _, _) => + Action runWorkload = (manager, _, workload) => { // See comments in test above for explanation. System.Threading.Thread.Sleep(100); @@ -177,11 +170,9 @@ public void OperationManagerMovesToTheNextWorkloadOnlyWhenRunNextWorkIsCalled() parallelOperationManager.RunNextWork(manager); } }; - Func initializeWorkload = (_, _, _) => - Task.Run(() => System.Threading.Thread.Sleep(100)); // Act - parallelOperationManager.StartWork(workloads, eventHandler, getEventHandler, initializeWorkload, runWorkload); + parallelOperationManager.StartWork(workloads, eventHandler, getEventHandler, runWorkload); // Assert // We start by scheduling 2 workloads (1 and 2) becuase that is the max parallel level. @@ -216,7 +207,7 @@ public void OperationManagerRunsAnOperationOnAllActiveManagersWhenDoActionOnAllM var eventHandler = new SampleHandler(); Func getEventHandler = (handler, _) => handler; - Action runWorkload = (manager, _, workload, _, _) => + Action runWorkload = (manager, _, workload) => { // See comments in test above for explanation. @@ -232,11 +223,9 @@ public void OperationManagerRunsAnOperationOnAllActiveManagersWhenDoActionOnAllM parallelOperationManager.RunNextWork(manager); } }; - Func initializeWorkload = (_, _, _) => - Task.Run(() => System.Threading.Thread.Sleep(100)); // Start the work, so we process workload 1 and then move to 2. - parallelOperationManager.StartWork(workloads, eventHandler, getEventHandler, initializeWorkload, runWorkload); + parallelOperationManager.StartWork(workloads, eventHandler, getEventHandler, runWorkload); // Act parallelOperationManager.DoActionOnAllManagers(manager => manager.Abort(), doActionsInParallel: true); From 9b63d78da9653f20d83b08bf37673fe1fcb584f9 Mon Sep 17 00:00:00 2001 From: dotnet bot Date: Thu, 2 Jul 2026 09:21:53 -0700 Subject: [PATCH 10/87] Localized file check-in by OneLocBuild Task: Build definition ID 1222: Build ID 3011640 (#16197) --- .../Resources/xlf/Resources.cs.xlf | 4 ++-- .../Resources/xlf/Resources.de.xlf | 4 ++-- .../Resources/xlf/Resources.es.xlf | 4 ++-- .../Resources/xlf/Resources.it.xlf | 4 ++-- .../Resources/xlf/Resources.ja.xlf | 4 ++-- .../Resources/xlf/Resources.ko.xlf | 4 ++-- .../Resources/xlf/Resources.pl.xlf | 4 ++-- .../Resources/xlf/Resources.pt-BR.xlf | 4 ++-- .../Resources/xlf/Resources.ru.xlf | 4 ++-- .../Resources/xlf/Resources.tr.xlf | 4 ++-- .../Resources/xlf/Resources.zh-Hans.xlf | 4 ++-- .../Resources/xlf/Resources.zh-Hant.xlf | 4 ++-- .../Resources/xlf/Resources.cs.xlf | 3 ++- .../Resources/xlf/Resources.de.xlf | 5 +++-- .../Resources/xlf/Resources.es.xlf | 9 ++++---- .../Resources/xlf/Resources.fr.xlf | 21 ++++++++++--------- .../Resources/xlf/Resources.it.xlf | 3 ++- .../Resources/xlf/Resources.ja.xlf | 3 ++- .../Resources/xlf/Resources.ko.xlf | 7 ++++--- .../Resources/xlf/Resources.pl.xlf | 3 ++- .../Resources/xlf/Resources.pt-BR.xlf | 11 +++++----- .../Resources/xlf/Resources.ru.xlf | 3 ++- .../Resources/xlf/Resources.tr.xlf | 7 ++++--- .../Resources/xlf/Resources.zh-Hans.xlf | 17 ++++++++------- .../Resources/xlf/Resources.zh-Hant.xlf | 5 +++-- 25 files changed, 79 insertions(+), 66 deletions(-) diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.cs.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.cs.xlf index 8fe92b0e8a..15b11d18cb 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.cs.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.cs.xlf @@ -30,9 +30,9 @@ Ověřte, že: Running .NET Framework tests is supported on Windows only. Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. - Running .NET Framework tests is supported on Windows only. + Spouštění testů .NET Framework je podporováno pouze ve Windows. -Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. +Pro spouštění testů .NET Framework v tomto operačním systému se využívalo řešení Mono, které již není podporováno. Chcete-li tyto testy spustit, spusťte je ve Windows nebo změňte testovací projekt tak, aby cílil na .NET místo na .NET Framework. diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.de.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.de.xlf index a4a148e621..1de47ae2bd 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.de.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.de.xlf @@ -30,9 +30,9 @@ Bestätigen Sie, dass: Running .NET Framework tests is supported on Windows only. Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. - Running .NET Framework tests is supported on Windows only. + Das Ausführen von .NET Framework-Tests wird nur unter Windows unterstützt. -Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. +Das Ausführen von .NET Framework-Tests unter diesem Betriebssystem basierte auf Mono, das nicht mehr unterstützt wird. Um diese Tests auszuführen, führen Sie sie unter Windows aus, oder ändern Sie das Testprojekt so, dass es auf .NET statt auf .NET Framework ausgerichtet ist. diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.es.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.es.xlf index 9f44a85e41..c45130a802 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.es.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.es.xlf @@ -30,9 +30,9 @@ Compruebe que: Running .NET Framework tests is supported on Windows only. Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. - Running .NET Framework tests is supported on Windows only. + La ejecución de pruebas de .NET Framework solo se admite en Windows. -Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. +La ejecución de pruebas de .NET Framework en este sistema operativo se basaba en Mono, que ya no se admite. Para ejecutar estas pruebas, ejecútelas en Windows o cambie el proyecto de prueba para que tenga como destino .NET en lugar de .NET Framework. diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.it.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.it.xlf index cc456effc7..abcaa1cbe8 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.it.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.it.xlf @@ -30,9 +30,9 @@ Verificare che: Running .NET Framework tests is supported on Windows only. Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. - Running .NET Framework tests is supported on Windows only. + L'esecuzione dei test .NET Framework è supportata solo su Windows. -Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. +L'esecuzione dei test .NET Framework su questo sistema operativo si basava su Mono, che non è più supportato. Per eseguire questi test, eseguili su Windows oppure modifica il progetto di test in modo che sia destinato a .NET anziché a .NET Framework. diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ja.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ja.xlf index 19326baa58..097826b511 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ja.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ja.xlf @@ -30,9 +30,9 @@ Verify that: Running .NET Framework tests is supported on Windows only. Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. - Running .NET Framework tests is supported on Windows only. + .NET Framework テストの実行は、Windows でのみサポートされています。 -Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. +このオペレーティング システムでの .NET Framework テストの実行は、サポートされなくなった Mono に依存していました。これらのテストを実行するには、Windows でテストを実行するか、テスト プロジェクトを .NET Framework ではなく .NET をターゲットに変更します。 diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ko.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ko.xlf index 5818e27d28..e7a693b6f7 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ko.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ko.xlf @@ -30,9 +30,9 @@ Verify that: Running .NET Framework tests is supported on Windows only. Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. - Running .NET Framework tests is supported on Windows only. + .NET Framework 테스트 실행은 Windows에서만 지원됩니다. -Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. +이 운영 체제에서 .NET Framework 테스트를 실행하려면 더 이상 지원되지 않는 Mono에 의존해야 했습니다. 이러한 테스트를 실행하려면 Windows에서 실행하거나, 테스트 프로젝트가 .NET Framework 대신 .NET을 대상으로 하도록 변경하세요. diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pl.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pl.xlf index 0376620b20..9afbd47246 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pl.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pl.xlf @@ -30,9 +30,9 @@ Sprawdź, czy: Running .NET Framework tests is supported on Windows only. Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. - Running .NET Framework tests is supported on Windows only. + Uruchamianie testów platformy .NET Framework jest obsługiwane tylko w systemie Windows. -Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. +Uruchamianie testów platformy .NET Framework w tym systemie operacyjnym opiera się na platformie Mono, która nie jest już obsługiwana. Aby uruchomić te testy, uruchom je w systemie Windows lub zmień projekt testowy na docelowy .NET zamiast .NET Framework. diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pt-BR.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pt-BR.xlf index a2a8567eeb..e06e0a68d7 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pt-BR.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.pt-BR.xlf @@ -30,9 +30,9 @@ Verifique se: Running .NET Framework tests is supported on Windows only. Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. - Running .NET Framework tests is supported on Windows only. + A .NET Framework testes é compatível apenas com o Windows. -Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. +Os testes do A .NET Framework neste sistema operacional se baseou no Mono, que não tem mais suporte. Para executar esses testes, execute-os no Windows ou altere o projeto de teste para o .NET de destino em vez de .NET Framework. diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ru.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ru.xlf index eccc6af287..c2d6af81ae 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ru.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.ru.xlf @@ -30,9 +30,9 @@ Verify that: Running .NET Framework tests is supported on Windows only. Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. - Running .NET Framework tests is supported on Windows only. + Запуск тестов .NET Framework поддерживается только в Windows. -Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. +Для запуска тестов .NET Framework в этой операционной системе использовался Mono, который больше не поддерживается. Чтобы запустить эти тесты, выполняйте их в Windows или измените тестовый проект, чтобы он использовал .NET вместо .NET Framework. diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.tr.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.tr.xlf index daef6fcd2b..fe025a3876 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.tr.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.tr.xlf @@ -30,9 +30,9 @@ Verify that: Running .NET Framework tests is supported on Windows only. Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. - Running .NET Framework tests is supported on Windows only. + .NET Framework testlerinin çalıştırılması yalnızca Windows'da desteklenir. -Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. +Bu işletim sisteminde .NET Framework testlerinin çalıştırılması, artık desteklenmeyen Mono'ya dayanıyordu. Bu testleri çalıştırmak için, bunları Windows'da çalıştırın veya test projesini .NET Framework yerine .NET'i hedefleyecek şekilde değiştirin. diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hans.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hans.xlf index fde22fbdda..a98354bad9 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hans.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hans.xlf @@ -30,9 +30,9 @@ Verify that: Running .NET Framework tests is supported on Windows only. Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. - Running .NET Framework tests is supported on Windows only. + 只有 Windows 支持运行 .NET Framework 测试。 -Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. +在此操作系统上运行 .NET Framework 测试依赖于 Mono,这已不再受支持。要运行这些测试,请在 Windows 上运行它们,或者将测试项目更改为面向 .NET 而不是.NET Framework。 diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hant.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hant.xlf index d9bf964ed0..c77254e527 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hant.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.zh-Hant.xlf @@ -31,9 +31,9 @@ Verify that: Running .NET Framework tests is supported on Windows only. Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. - Running .NET Framework tests is supported on Windows only. + 只有 Windows 支援執行 .NET Framework 測試。 -Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. +在此作業系統上執行 .NET Framework 測試時,會依賴 Mono,但系統已不再支援 Mono。若要執行這些測試,請在 Windows 上執行,或將測試專案的目標從 .NET Framework 改為 .NET。 diff --git a/src/vstest.console/Resources/xlf/Resources.cs.xlf b/src/vstest.console/Resources/xlf/Resources.cs.xlf index d6bff3c5fa..529c20986f 100644 --- a/src/vstest.console/Resources/xlf/Resources.cs.xlf +++ b/src/vstest.console/Resources/xlf/Resources.cs.xlf @@ -868,7 +868,7 @@ Example: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full - --Blame|/Blame:[CollectDump];[CollectAlways]=[Value];[DumpType]=[Value] + --Blame|/Blame:[CollectDump];[CollectAlways]=[Value];[DumpType]=[Value] Spustí test v režimu blame. Tato možnost je užitečná při izolaci problematického testu, který způsobuje jeho chybové ukončení. Vytvoří výstupní soubor v aktuálním adresáři jako Sequence.xml, který zachytí pořadí provádění testu před chybovým ukončením. @@ -877,6 +877,7 @@ Toto výchozí chování můžete také přepsat pomocí určitých volitelných parametrů: CollectAlways – Pro shromáždění výpisu při ukončení, pokud nedojde k chybovému ukončení (true/false) DumpType – Pro zadání typu výpisu (mini/full) + Shromažďování výpisu paměti při selhání ve Windows vyžaduje, aby byly soubory procdump.exe a procdump64.exe dostupné v proměnné PATH nebo v adresáři určeném proměnnou prostředí PROCDUMP_PATH. Nástroje si můžete stáhnout z https://docs.microsoft.com/sysinternals/downloads/procdump. Příklad: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full diff --git a/src/vstest.console/Resources/xlf/Resources.de.xlf b/src/vstest.console/Resources/xlf/Resources.de.xlf index a2414da2f6..c13084e348 100644 --- a/src/vstest.console/Resources/xlf/Resources.de.xlf +++ b/src/vstest.console/Resources/xlf/Resources.de.xlf @@ -868,15 +868,16 @@ Example: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full - --Blame|/Blame:[CollectDump];[CollectAlways]=[Value];[DumpType]=[Value] + --Blame|/Blame:[CollectDump];[CollectAlways]=[Value];[DumpType]=[Value] Führt den Test im blame-Modus aus. Diese Option ist hilfreich zum Isolieren des problematischen Tests, der einen Absturz des Testhosts verursacht. Im aktuellen Verzeichnis wird eine Ausgabedatei namens "Sequence.xml" erstellt, in der die Reihenfolge der Testausführung vor dem Absturz erfasst wird. Optional können Sie eine Prozesssicherung für den Testhost erfassen. Wenn Sie sich zum Erfassen einer Sicherung entschließen, wird bei einem Absturz standardmäßig eine Minisicherung erfasst. Dieses Standardverhalten können Sie durch einige optionale Parameter außer Kraft setzen: - CollectAlways: Zum Erfassen einer Sicherung beim Beenden, selbst wenn kein Absturz eintritt (TRUE/FALSE). + CollectAlways: Zum Erfassen einer Sicherung beim Beenden, selbst wenn kein Absturz eintritt (TRUE/FALSE). DumpType: Zur Angabe des Sicherungstyps (mini/full). + Das Erfassen einer Absturzsicherung unter Windows erfordert, dass procdump.exe und procdump64.exe im PATH verfügbar sind oder sich in einem Verzeichnis befinden, auf das die Umgebungsvariable PROCDUMP_PATH verweist. Die Tools können hier heruntergeladen werden://docs.microsoft.com/sysinternals/downloads/procdump. Beispiel: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full diff --git a/src/vstest.console/Resources/xlf/Resources.es.xlf b/src/vstest.console/Resources/xlf/Resources.es.xlf index 1b241cca88..23704397e3 100644 --- a/src/vstest.console/Resources/xlf/Resources.es.xlf +++ b/src/vstest.console/Resources/xlf/Resources.es.xlf @@ -871,15 +871,16 @@ Example: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full - --Blame|/Blame:[CollectDump];[CollectAlways]=[Value];[DumpType]=[Value] + --Blame|/Blame:[CollectDump];[CollectAlways]=[Value];[DumpType]=[Value] Ejecuta la prueba en modo blame. Esta opción es útil a la hora de aislar la prueba problemática que provoca el bloqueo del host de prueba. Crea un archivo de salida en el directorio actual como "Sequence.xml", que captura el orden de ejecución de la prueba antes del bloqueo. Tiene la opción de elegir recopilar el volcado de proceso para el host de prueba. - Cuando elige recopilar el volcado, de forma predeterminada se recopila un mini volcado en un bloqueo. + Cuando elige recopilar el volcado, de forma predeterminada se recopilará un mini volcado en un bloqueo. También puede elegir invalidar este comportamiento predeterminado mediante algunos parámetros opcionales: - CollectAlways: para recopilar un volcado al salir incluso aunque no haya un bloqueo (true/false) - DumpType: para especificar el tipo de volcado (mini/completo). + CollectAlways: para recopilar un volcado al salir incluso aunque no haya un bloqueo (true/false) + DumpType: para especificar el tipo de volcado (mini/full). + La recopilación de un volcado de memoria en Windows requiere que procdump.exe y procdump64.exe estén disponibles en PATH o en un directorio al que apunta la variable de entorno PROCDUMP_PATH. Las herramientas se pueden descargar desde aquí: https://docs.microsoft.com/sysinternals/downloads/procdump. Ejemplo: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full diff --git a/src/vstest.console/Resources/xlf/Resources.fr.xlf b/src/vstest.console/Resources/xlf/Resources.fr.xlf index 39e0b33990..7083ae560b 100644 --- a/src/vstest.console/Resources/xlf/Resources.fr.xlf +++ b/src/vstest.console/Resources/xlf/Resources.fr.xlf @@ -868,16 +868,17 @@ Example: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full - --Blame|/Blame:[CollectDump];[CollectAlways]=[Value];[DumpType]=[Value] - Exécute le test en mode blame. Cette option est utile pour isoler le test problématique qui cause le plantage de l'hôte de test. - Il crée un fichier de sortie dans le répertoire actif nommé "Sequence.xml", - qui capture l'ordre d'exécution du test avant le plantage. - Vous pouvez éventuellement choisir de collecter le fichier dump du processus de l'hôte de test. - Quand vous choisissez de collecter le fichier dump, par défaut, un mini-dump est collecté lors du plantage. - Vous pouvez aussi choisir de remplacer ce comportement par défaut par des paramètres facultatifs : - CollectAlways - Pour collecter le fichier dump à la fermeture, même s'il n'y pas de plantage (true/false) - DumpType - Pour spécifier le type de fichier dump (mini/full). - Exemple : /Blame + --Blame|/Blame:[CollectDump];[CollectAlways]=[Value];[DumpType]=[Value] + Exécute le test en mode blâme. Cette option est utile pour isoler le test problématique provoquant le crash de l'hôte de test. + Il crée un fichier de sortie dans le répertoire courant comme "Sequence.xml", + qui capture l'ordre d'exécution des tests avant le crash. + Vous pouvez éventuellement choisir de collecter le vidage de processus pour l'hôte de test. + Lorsque vous choisissez de collecter le vidage, par défaut, un mini vidage sera collecté lors d'un crash. + Vous pouvez également choisir de remplacer ce comportement par défaut par certains paramètres facultatifs : + CollectAlways - Pour collecter le vidage à la sortie, même s’il n’y a pas d’incident (true/false) + DumpType – Pour spécifier le type de vidage (mini/complet). + La collecte d’un vidage sur incident sur Windows nécessite que procdump.exe et procdump64.exe soient disponibles dans PATH ou dans un répertoire désigné par la variable d’environnement PROCDUMP_PATH. Les outils peuvent être téléchargés à partir de https://docs.microsoft.com/sysinternals/downloads/procdump. + Exemple: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full diff --git a/src/vstest.console/Resources/xlf/Resources.it.xlf b/src/vstest.console/Resources/xlf/Resources.it.xlf index 9569b31f31..211b402987 100644 --- a/src/vstest.console/Resources/xlf/Resources.it.xlf +++ b/src/vstest.console/Resources/xlf/Resources.it.xlf @@ -868,7 +868,7 @@ Example: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full - --Blame|/Blame:[CollectDump];[CollectAlways]=[Valore];[DumpType]=[Valore] + --Blame|/Blame:[CollectDump];[CollectAlways]=[Valore];[DumpType]=[Valore] Esegue il test in modalità di segnalazione errore. Questa opzione è utile per isolare il test problematico che causa l'arresto anomalo di Test Host. Crea nella directory corrente un file di output "Sequence.xml", che acquisisce l'ordine di esecuzione del test prima dell'arresto anomalo. @@ -877,6 +877,7 @@ Si può anche scegliere di eseguire l'override di questo comportamento predefinito usando alcuni parametri facoltativi: CollectAlways: per raccogliere il dump all'uscita anche in assenza di arresto anomalo (true/false) DumpType: per specificare il tipo di dump (mini/full). + La raccolta di un dump di arresto anomalo in Windows richiede che procdump.exe e procdump64.exe siano disponibili nel PATH oppure in una directory indicata dalla variabile di ambiente PROCDUMP_PATH. Gli strumenti possono essere scaricati da https://docs.microsoft.com/sysinternals/downloads/procdump. Esempio: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full diff --git a/src/vstest.console/Resources/xlf/Resources.ja.xlf b/src/vstest.console/Resources/xlf/Resources.ja.xlf index b1312fbbec..943ef49942 100644 --- a/src/vstest.console/Resources/xlf/Resources.ja.xlf +++ b/src/vstest.console/Resources/xlf/Resources.ja.xlf @@ -868,7 +868,7 @@ Example: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full - --Blame|/Blame:[CollectDump];[CollectAlways]=[Value];[DumpType]=[Value] + --Blame|/Blame:[CollectDump];[CollectAlways]=[Value];[DumpType]=[Value] テストを Blame モードで実行します。このオプションは、テスト ホストのクラッシュを引き起こしているテストを分離するのに役立ちます。 現在のディレクトリに "Sequence.xml" という出力ファイルを作成し、 そのファイルにクラッシュ前のテストの実行順序をキャプチャします。 @@ -877,6 +877,7 @@ この既定の動作を次のオプション パラメーターでオーバーライドすることもできます: CollectAlways - クラッシュが発生していなくても終了時にダンプを収集します (true/false) DumpType - ダンプの種類を指定します (mini/full)。 + Windows でクラッシュ ダンプを収集するには、PATH または PROCDUMP_PATH 環境変数が指すディレクトリで procdump.exe と procdump64.exe を使用できる必要があります。ツールは次の場所からダウンロードできます: https://docs.microsoft.com/sysinternals/downloads/procdump。 例: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full diff --git a/src/vstest.console/Resources/xlf/Resources.ko.xlf b/src/vstest.console/Resources/xlf/Resources.ko.xlf index 7e70614558..eb6cc76f10 100644 --- a/src/vstest.console/Resources/xlf/Resources.ko.xlf +++ b/src/vstest.console/Resources/xlf/Resources.ko.xlf @@ -868,15 +868,16 @@ Example: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full - --Blame|/Blame:[CollectDump];[CollectAlways]=[Value];[DumpType]=[Value] + --Blame|/Blame:[CollectDump];[CollectAlways]=[Value];[DumpType]=[Value] 원인 모드에서 테스트를 실행합니다. 이 옵션은 테스트 호스트 크래시를 유발하는 문제 있는 테스트를 격리하는 데 유용합니다. 이 경우 현재 디렉터리에 "Sequence.xml"라는 출력 파일을 만들며, 이 파일에는 크래시 이전 테스트 실행 순서가 캡처되어 있습니다. 필요에 따라 테스트 호스트에 대한 프로세스 덤프를 수집하도록 선택할 수 있습니다. 덤프를 수집하도록 선택하는 경우 기본적으로 크래시에 대한 미니 덤프가 수집됩니다. 다음과 같은 몇 가지 선택적 매개 변수로 이 기본 동작을 재정의하도록 선택할 수도 있습니다. - CollectAlways - 크래시가 없는 경우에도 종료 시 덤프를 수집함(true/false) - DumpType - 덤프 유형을 지정함(mini/full). + CollectAlways - 크래시가 없는 경우에도 종료 시 덤프를 수집(true/false) + DumpType - 덤프 유형을 지정(mini/full). + Windows에서 크래시 덤프를 수집하려면 procdump.exe 및 procdump64.exe가 PATH에 있거나 PROCDUMP_PATH 환경 변수가 가리키는 디렉터리에 있어야 합니다. 도구는 다음 페이지에서 다운로드할 수 있습니다. https://docs.microsoft.com/sysinternals/downloads/procdump 예: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full diff --git a/src/vstest.console/Resources/xlf/Resources.pl.xlf b/src/vstest.console/Resources/xlf/Resources.pl.xlf index 7b81d06f2b..e5375dbb77 100644 --- a/src/vstest.console/Resources/xlf/Resources.pl.xlf +++ b/src/vstest.console/Resources/xlf/Resources.pl.xlf @@ -868,7 +868,7 @@ Example: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full - --Blame|/Blame:[CollectDump];[CollectAlways]=[Value];[DumpType]=[Value] + --Blame|/Blame:[CollectDump];[CollectAlways]=[Value];[DumpType]=[Value] Uruchamia test w trybie Blame. Ta opcja jest pomocna w odizolowaniu problematycznego testu powodującego awarię hosta testów. Tworzy w bieżącym katalogu plik wyjściowy „Sequence.xml”, który przechwytuje kolejność wykonywania testu przed awarią. @@ -877,6 +877,7 @@ Możesz także przesłonić to zachowanie domyślne opcjonalnymi parametrami: CollectAlways — zrzut jest zbierany przy wychodzeniu, nawet jeśli nie ma awarii (true/false) DumpType — określa typ zrzutu (mini/full). + Aby zebrać zrzut awaryjny w systemie Windows, w ścieżce PATH muszą być dostępne pliki procdump.exe i procdump64.exe albo muszą znajdować się w katalogu wskazanym przez zmienną środowiskową PROCDUMP_PATH. Narzędzia można pobrać z https://docs.microsoft.com/sysinternals/downloads/procdump. Przykład: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full diff --git a/src/vstest.console/Resources/xlf/Resources.pt-BR.xlf b/src/vstest.console/Resources/xlf/Resources.pt-BR.xlf index 7c1f8742ef..b8db02ec03 100644 --- a/src/vstest.console/Resources/xlf/Resources.pt-BR.xlf +++ b/src/vstest.console/Resources/xlf/Resources.pt-BR.xlf @@ -868,15 +868,16 @@ Alterar o nível de rastreamento dos logs, como mostrado abaixo Example: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full - --Blame|/Blame:[CollectDump];[CollectAlways]=[Value];[DumpType]=[Value] - Executa o teste em modo blame. Essa opção é útil para isolar o teste com problema causando falha no host de teste . - Ele cria um arquivo de saída no diretório atual como "Sequence.xml", + --Blame|/Blame:[CollectDump];[CollectAlways]=[Value];[DumpType]=[Value] + Executa o teste em modo blame. Essa opção é útil para isolar o teste com problema causando falha no host de teste. + Ele cria um arquivo de saída no diretório atual como "Sequence.xml", que captura a ordem de execução do teste antes da falha. Você pode optar por coletar o processo de despejo para o host de teste. Ao optar por coletar despejo, por padrão, um mini despejo será coletado em uma falha. - Você também pode optar por sobrescrever este comportamento padrão por alguns parâmetros opcionais: - CollectAlways - Para coletar despejo na saída mesmo que não haja falha (true/false) + Você também pode optar por sobrescrever este comportamento padrão por alguns parâmetros opcionais: + CollectAlways — Para coletar despejo na saída mesmo se não houver nenhuma falha (true/false) DumpType - Para especificar o tipo de despejo (mini/full). + Coletar um despejo de memória no Windows requer que procdump.exe e procdump64.exe estejam disponíveis no PATH ou em um diretório apontado pela variável de ambiente PROCDUMP_PATH. As ferramentas podem ser baixadas em https://docs.microsoft.com/sysinternals/downloads/procdump. Exemplo: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full diff --git a/src/vstest.console/Resources/xlf/Resources.ru.xlf b/src/vstest.console/Resources/xlf/Resources.ru.xlf index 87f6984be1..8bb5330cdc 100644 --- a/src/vstest.console/Resources/xlf/Resources.ru.xlf +++ b/src/vstest.console/Resources/xlf/Resources.ru.xlf @@ -868,7 +868,7 @@ Example: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full - --Blame|/Blame:[CollectDump];[CollectAlways]=[значение];[DumpType]=[значение] + --Blame|/Blame:[CollectDump];[CollectAlways]=[значение];[DumpType]=[значение] Выполняет тест в blame-режиме. Этот вариант помогает изолировать проблемный тест, вызывающий сбой хоста для тестов. В текущем каталоге создается выходной файл "Sequence.xml", который записывает порядок выполнения теста перед сбоем. @@ -877,6 +877,7 @@ Вы также можете переопределить поведение по умолчанию с помощью дополнительных параметров: CollectAlways — собирает дамп по завершении даже при отсутствии сбоев (true/false) DumpType — задает тип дампа, mini (мини-дамп) или full (полный). + Для создания дампа аварийного дампа в Windows необходимо, чтобы файлы procdump.exe и procdump64.exe находились в одном из каталогов, указанных в переменной окружения PATH, либо в каталоге, на который указывает переменная окружения PROCDUMP_PATH. Эти инструменты можно загрузить по адресу https://docs.microsoft.com/sysinternals/downloads/procdump. Пример: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full diff --git a/src/vstest.console/Resources/xlf/Resources.tr.xlf b/src/vstest.console/Resources/xlf/Resources.tr.xlf index 9462010a5e..b6f8b3a7f8 100644 --- a/src/vstest.console/Resources/xlf/Resources.tr.xlf +++ b/src/vstest.console/Resources/xlf/Resources.tr.xlf @@ -868,15 +868,16 @@ Günlükler için izleme düzeyini aşağıda gösterildiği gibi değiştirin Example: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full - --Blame|/Blame:[CollectDump];[CollectAlways]=[Value];[DumpType]=[Value] + --Blame|/Blame:[CollectDump];[CollectAlways]=[Value];[DumpType]=[Value] Testi blame modunda çalıştırır. Bu seçenek, test ana bilgisayarının kilitlenmesine neden olan sorunlu testin ayrı tutulmasını sağlar. - Geçerli dizinde, kilitlenmeden önce testin yürütülme düzenini yakalayan, - “Sequence.xml” adlı bir çıkış dosyası oluşturur. + Geçerli dizinde “Sequence.xml” adlı bir çıktı dosyası oluşturur, + kilitlenmeden önce testin yürütülme düzenini yakalayan. İsteğe bağlı olarak test ana bilgisayarının işlem dökümünü çıkarmayı seçebilirsiniz. Dökümü çıkarmayı seçtiğinizde, kilitlenme durumunda varsayılan olarak bir mini döküm çıkarılır. Dilerseniz bazı isteğe bağlı parametrelerle bu varsayılan davranışı geçersiz kılabilirsiniz: CollectAlways - Kilitlenme olmasa da her çıkışta döküm çıkarma (true/false) DumpType - Döküm türünü belirtme (mini/full). + Windows'da kilitlenme bilgi dökümü toplamak için procdump.exe ve procdump64.exe dosyalarının PATH içinde ya da PROCDUMP_PATH ortam değişkeninin işaret ettiği bir dizinde bulunması gerekir. Araçlar https://docs.microsoft.com/sysinternals/downloads/procdump adresinden indirilebilir. Örnek: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full diff --git a/src/vstest.console/Resources/xlf/Resources.zh-Hans.xlf b/src/vstest.console/Resources/xlf/Resources.zh-Hans.xlf index cb24330f88..0c108eb48c 100644 --- a/src/vstest.console/Resources/xlf/Resources.zh-Hans.xlf +++ b/src/vstest.console/Resources/xlf/Resources.zh-Hans.xlf @@ -868,15 +868,16 @@ Example: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full - --Blame|/Blame:[CollectDump];[CollectAlways]=[Value];[DumpType]=[Value] - 在追责模式下运行测试。此选项有助于隔离导致测试主机崩溃的问题测试。 - 它会在当前目录中创建一个输出文件 "Sequence.xml", - 用于在崩溃之前捕获测试的执行顺序。 - 可以根据需要选择收集测试主机的进程转储。 - 选择收集转储时,默认情况下,将在崩溃时收集小型转储。 - 还可选择通过一些可选参数覆盖此默认行为: - CollectAlways - 若无崩溃则在退出时收集转储(true/false) + --Blame|/Blame:[CollectDump];[CollectAlways]=[Value];[DumpType]=[Value] + 在追溯模式下运行测试。此选项有助于隔离导致测试主机故障的问题测试。 + 它会在当前目录中创建一个输出文件 "Sequence.xml", + 用于在发生故障之前捕获测试的执行顺序。 + 你可以根据需要选择收集测试主机的进程转储。 + 选择收集转储时,默认情况下,将在发生故障时收集小型转储。 + 你还可选择通过一些可选参数覆盖此默认行为: + CollectAlways - 即使没有故障,也会在退出时收集转储(true/false) DumpType - 指定转储类型(小型/完整)。 + 在 Windows 上收集故障转储时,需要 procdump.exe 和 procdump64.exe 位于 PATH 中,或者位于 PROCDUMP_PATH 环境变量指定的目录中。可在以下位置下载工具: https://docs.microsoft.com/sysinternals/downloads/procdump。 Example: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full diff --git a/src/vstest.console/Resources/xlf/Resources.zh-Hant.xlf b/src/vstest.console/Resources/xlf/Resources.zh-Hant.xlf index 137cdc47fe..b230caff98 100644 --- a/src/vstest.console/Resources/xlf/Resources.zh-Hant.xlf +++ b/src/vstest.console/Resources/xlf/Resources.zh-Hant.xlf @@ -868,15 +868,16 @@ Example: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full - --Blame|/Blame:[CollectDump];[CollectAlways]=[Value];[DumpType]=[Value] + --Blame|/Blame:[CollectDump];[CollectAlways]=[Value];[DumpType]=[Value] 以 blame 模式執行測試。此選項在隔離造成測試主機當機的問題測試時相當實用。 它會在目前的目錄建立輸出檔案 "Sequence.xml", 該檔案會擷取當機前的測試執行順序。 您也可以選擇收集測試主機的處理序傾印。 - 當您選擇收集傾印時,預設會在當機時收集迷你傾印/ + 當您選擇收集傾印時,預設會在當機時收集迷你傾印。 您也可以選擇使用一些選用參數來覆寫此預設行為: CollectAlways - 即使沒有當機,也在離開時收集傾印 (true/false) DumpType - 指定傾印類型 (mini/full)。 + 在 Windows 上收集當機傾印需要於 PATH 或於 PROCDUMP_PATH 環境變數所指向的目錄中提供 procdump.exe 和 procdump64.exe。您可以於下列位置下載工具: https://docs.microsoft.com/sysinternals/downloads/procdump。 範例: /Blame /Blame:CollectDump /Blame:CollectDump;CollectAlways=true;DumpType=full From fdd72c52801887e61c69c91e93abdb655723176b Mon Sep 17 00:00:00 2001 From: Missy Messa <47990216+missymessa@users.noreply.github.com> Date: Fri, 3 Jul 2026 01:02:18 -0700 Subject: [PATCH 11/87] Remove dead DotNet-VSTS-Infra-Access variable group reference (#16203) The DotNet-VSTS-Infra-Access variable group (VG 4 in dnceng/internal) only contained the dn-bot-devdiv-drop-r-code-r PAT which has been migrated to WIF. The PAT is expired and disabled. The variable group reference is no longer needed since DevDiv drop access token is now acquired via WIF service connection. --- azure-pipelines-official.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/azure-pipelines-official.yml b/azure-pipelines-official.yml index ae2dbf895e..657a7f1190 100644 --- a/azure-pipelines-official.yml +++ b/azure-pipelines-official.yml @@ -91,8 +91,6 @@ variables: - name: Codeql.Cadence value: 0 - # Group gives access to $dn-bot-devdiv-drop-rw-code-rw and dn-bot-dnceng-build-rw-code-rw - - group: DotNet-VSTS-Infra-Access # DevDiv drop access token is acquired via WIF service connection (dnceng-devdiv-drop-rw-code-rw-wif) - name: _DevDivDropAccessToken value: '' From 6e3e43cda0a0819b9ee94cfceb3e8c759a5c7658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Fri, 3 Jul 2026 12:14:33 +0200 Subject: [PATCH 12/87] Inject IRunSettingsProvider into vstest.console argument processors (#16200) * Inject IRunSettingsProvider into vstest.console argument processors vstest.console built every argument processor with a parameterless ctor and read RunSettingsManager.Instance internally, so the active runsettings were shared process-wide and awkward to isolate in tests. Thread an IRunSettingsProvider from Executor through ArgumentProcessorFactory into the 18 processors that consume runsettings; the factory defaults to RunSettingsManager.Instance so behavior is unchanged. RunTests/RunSpecificTests also flow the provider into their nested TestRunRequestEventsRegistrar. First step toward removing mutable static state from the console. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix vstest-build-test skill: correct smoke/integration test commands The skill told you to run smoke tests with `-projects smoke` / `-p smoke`, but `-projects` is passed through Resolve-Path and only accepts a real path/glob, so a bare category name fails with "Cannot find path". Smoke/integration/perf/compat are switches (`-smokeTest`, `-integrationTest`, ...). Also documented the Windows DOTNET_ROOT gotcha: test.cmd (unlike test.sh) doesn't set DOTNET_ROOT, so the self-hosted preview-TFM apphosts fail to launch until you point it at the repo .dotnet. Dropped the TestRunnerAdditionalArguments --filter example that Build.ps1 now rejects in favor of the -filter parameter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review comments The ListFullyQualifiedTests metadata test was constructing ListTestsArgumentProcessor and asserting ListTestsArgumentProcessorCapabilities, so it never covered the processor its name claims. Point it at ListFullyQualifiedTestsArgumentProcessor. Also fix the skill doc: eng/build.ps1 casing (case-sensitive on Linux/macOS) and scope the .dotnet/dotnet.exe example to Windows with a dotnet variant for Linux/macOS. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/skills/vstest-build-test/SKILL.md | 80 +++++++++++++++---- src/vstest.console/CommandLine/Executor.cs | 12 ++- .../CLIRunSettingsArgumentProcessor.cs | 8 +- .../Processors/CollectArgumentProcessor.cs | 8 +- .../EnableBlameArgumentProcessor.cs | 6 +- .../EnableCodeCoverageArgumentProcessor.cs | 8 +- .../EnableLoggerArgumentProcessor.cs | 8 +- .../EnvironmentArgumentProcessor.cs | 8 +- .../Processors/FrameworkArgumentProcessor.cs | 8 +- .../InIsolationArgumentProcessor.cs | 8 +- ...istFullyQualifiedTestsArgumentProcessor.cs | 8 +- .../Processors/ListTestsArgumentProcessor.cs | 8 +- .../Processors/ParallelArgumentProcessor.cs | 8 +- .../Processors/PlatformArgumentProcessor.cs | 8 +- .../ResultsDirectoryArgumentProcessor.cs | 8 +- .../RunSettingsArgumentProcessor.cs | 8 +- .../RunSpecificTestsArgumentProcessor.cs | 19 +++-- .../Processors/RunTestsArgumentProcessor.cs | 19 +++-- ...AdapterLoadingStrategyArgumentProcessor.cs | 8 +- .../TestAdapterPathArgumentProcessor.cs | 8 +- .../Utilities/ArgumentProcessorFactory.cs | 50 +++++++----- .../CLIRunSettingsArgumentProcessorTests.cs | 6 +- .../CollectArgumentProcessorTests.cs | 6 +- .../EnableBlameArgumentProcessorTests.cs | 6 +- ...nableCodeCoverageArgumentProcessorTests.cs | 6 +- .../EnableLoggersArgumentProcessorTests.cs | 6 +- .../FrameworkArgumentProcessorTests.cs | 4 +- .../InIsolationArgumentProcessorTests.cs | 6 +- ...llyQualifiedTestsArgumentProcessorTests.cs | 6 +- .../ListTestsArgumentProcessorTests.cs | 4 +- .../ParallelArgumentProcessorTests.cs | 4 +- .../PlatformArgumentProcessorTests.cs | 4 +- .../ResultsDirectoryArgumentProcessorTests.cs | 4 +- .../RunSettingsArgumentProcessorTests.cs | 6 +- .../RunSpecificTestsArgumentProcessorTests.cs | 4 +- .../RunTestsArgumentProcessorTests.cs | 4 +- .../TestAdapterPathArgumentProcessorTests.cs | 6 +- .../ArgumentProcessorFactoryTests.cs | 9 ++- 38 files changed, 290 insertions(+), 107 deletions(-) diff --git a/.github/skills/vstest-build-test/SKILL.md b/.github/skills/vstest-build-test/SKILL.md index 3c061b2c28..4a22c79102 100644 --- a/.github/skills/vstest-build-test/SKILL.md +++ b/.github/skills/vstest-build-test/SKILL.md @@ -90,30 +90,75 @@ For isolated projects with few dependencies: ./test.cmd ``` -### Specific Test Assembly +### Specific Project(s) -Use `-p` to filter by assembly name pattern: +`-projects` / `--projects` takes a **resolvable path or glob** — it is passed through +`Resolve-Path`, so a bare project nickname or category (e.g. `smoke`, `htmllogger`) fails with +`Cannot find path`. Point it at the csproj(s): ```bash +# Windows +./test.cmd -projects "test\**\*HtmlLogger*\*.csproj" + # Linux / macOS -./test.sh -p htmllogger # HTML logger tests -./test.sh -p trxlogger # TRX logger tests -./test.sh -p datacollector # Data collector tests -./test.sh -p smoke # Smoke tests - -# Windows (-p is ambiguous in PowerShell; use -projects) -./test.cmd -projects htmllogger -./test.cmd -projects smoke +./test.sh --projects "test/**/*HtmlLogger*/*.csproj" ``` -### Specific Test by Name +For a single project you can also build+test its csproj directly with the bootstrapped SDK: ```bash # Windows -./test.cmd -bl -c release /p:TestRunnerAdditionalArguments="'--filter TestName'" -Integration +./.dotnet/dotnet.exe test test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/*.csproj -c Debug # Linux / macOS -./test.sh -bl -c release /p:TestRunnerAdditionalArguments="'--filter TestName'" --integrationTest +./.dotnet/dotnet test test/Microsoft.TestPlatform.Extensions.HtmlLogger.UnitTests/*.csproj -c Debug +``` + +### Test Categories (smoke / integration / performance / compatibility) + +These are **switches** handled by `eng/build.ps1` — NOT `-projects` values: + +```bash +# Windows +./test.cmd -smokeTest # TestCategory=Smoke (a subset of integration tests) +./test.cmd -integrationTest # full acceptance / integration suite +./test.cmd -performanceTest +./test.cmd -compatibilityTest + +# Linux / macOS use the same switch names +./test.sh -smokeTest +``` + +> `-smokeTest` and `-integrationTest` are mutually exclusive (smoke is a subset); passing both throws. + +### Filter by Test Name + +Use the `-filter` parameter. Do **not** pass `--filter` inside `TestRunnerAdditionalArguments` — +`eng/build.ps1` explicitly throws if you do. + +```bash +# Windows +./test.cmd -integrationTest -filter "FullyQualifiedName~MyScenario" + +# Linux / macOS +./test.sh -integrationTest -filter "FullyQualifiedName~MyScenario" +``` + +### Running integration / smoke tests locally (DOTNET_ROOT gotcha) + +Integration and smoke tests self-host: they launch test-asset apphosts built against the repo's +preview TFM (e.g. `net11.0`). An apphost resolves its shared runtime from `DOTNET_ROOT`, falling +back to the machine-wide install (`C:\Program Files\dotnet`), which usually lacks the preview +runtime — so it fails instantly with *"You must install or update .NET to run this application."* + +- `test.sh` (Linux/macOS) sets `DOTNET_ROOT` to the repo `.dotnet` automatically. +- `test.cmd` (Windows) does **not** — set it yourself before running: + +```powershell +$env:DOTNET_ROOT = "$PWD\.dotnet" +${env:DOTNET_ROOT(x86)} = "$PWD\.dotnet\dotnet-sdk-x86" # only if x86 test hosts run +$env:DOTNET_MULTILEVEL_LOOKUP = "0" +./test.cmd -smokeTest ``` ## Manual Validation with vstest.console @@ -131,15 +176,16 @@ After building with `--pack` / `-pack`, validate vstest.console changes by unzip ## Test Categories -| Category | Speed | What it tests | Filter | +| Category | Speed | What it tests | How to run | |---|---|---|---| -| Unit tests | Fast | Individual units | `./test.sh` / `./test.cmd` (default) | -| Smoke tests | Slow | P0 end-to-end scenarios | `-p smoke` | -| Acceptance tests | Slowest | Extensive coverage | `--integrationTest` / `-Integration` flag | +| Unit tests | Fast | Individual units | `./test.cmd` / `./test.sh` (default) | +| Smoke tests | Slow | P0 end-to-end scenarios | `-smokeTest` switch | +| Acceptance / integration | Slowest | Extensive coverage | `-integrationTest` switch | ## Troubleshooting - **OS mismatch errors:** If you see SDK load failures, run the mismatch detection script above to clean and re-bootstrap. +- **Integration/smoke tests fail instantly on Windows with "You must install or update .NET to run this application":** `test.cmd` does not set `DOTNET_ROOT`, so the self-hosted preview-TFM apphosts look in `C:\Program Files\dotnet` (which lacks the preview runtime). Set `$env:DOTNET_ROOT = "$PWD\.dotnet"` before running — see "Running integration / smoke tests locally". - If build fails asking for .NET 4.6 targeting pack, install it from [Microsoft Downloads](https://www.microsoft.com/download/details.aspx?id=48136) - Enable verbose diagnostics: see `docs/diagnose.md` - For debugging, add `Debugger.Launch` at process entry points (testhost.exe, vstest.console.exe) diff --git a/src/vstest.console/CommandLine/Executor.cs b/src/vstest.console/CommandLine/Executor.cs index a153dd4957..11f996557c 100644 --- a/src/vstest.console/CommandLine/Executor.cs +++ b/src/vstest.console/CommandLine/Executor.cs @@ -15,6 +15,7 @@ using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; using Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers; using Microsoft.VisualStudio.TestPlatform.Common; +using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; using Microsoft.VisualStudio.TestPlatform.Common.Utilities; using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing; using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.Interfaces; @@ -59,6 +60,7 @@ internal class Executor private readonly ITestPlatformEventSource _testPlatformEventSource; private readonly IProcessHelper _processHelper; private readonly IEnvironment _environment; + private readonly IRunSettingsProvider _runSettingsProvider; private bool _showHelp; /// @@ -89,6 +91,11 @@ internal class Executor } internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSource, IProcessHelper processHelper, IEnvironment environment) + : this(output, testPlatformEventSource, processHelper, environment, RunSettingsManager.Instance) + { + } + + internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSource, IProcessHelper processHelper, IEnvironment environment, IRunSettingsProvider runSettingsProvider) { DebuggerBreakpoint.AttachVisualStudioDebugger(WellKnownDebugEnvironmentVariables.VSTEST_RUNNER_DEBUG_ATTACHVS); DebuggerBreakpoint.WaitForNativeDebugger(WellKnownDebugEnvironmentVariables.VSTEST_RUNNER_NATIVE_DEBUG); @@ -99,6 +106,7 @@ internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSour _showHelp = true; _processHelper = processHelper; _environment = environment; + _runSettingsProvider = runSettingsProvider; } /// @@ -220,7 +228,7 @@ private int GetArgumentProcessors(string[] args, out List pr { processors = new List(); int result = 0; - var processorFactory = ArgumentProcessorFactory.Create(); + var processorFactory = ArgumentProcessorFactory.Create(runSettingsProvider: _runSettingsProvider); for (var index = 0; index < args.Length; index++) { var arg = args[index]; @@ -268,7 +276,7 @@ private int GetArgumentProcessors(string[] args, out List pr } // Initialize Runsettings with defaults - RunSettingsManager.Instance.AddDefaultRunSettings(); + _runSettingsProvider.AddDefaultRunSettings(); // Ensure we have an action argument. EnsureActionArgumentIsPresent(processors, processorFactory); diff --git a/src/vstest.console/Processors/CLIRunSettingsArgumentProcessor.cs b/src/vstest.console/Processors/CLIRunSettingsArgumentProcessor.cs index 20b0c677b3..a6ddc77bc6 100644 --- a/src/vstest.console/Processors/CLIRunSettingsArgumentProcessor.cs +++ b/src/vstest.console/Processors/CLIRunSettingsArgumentProcessor.cs @@ -29,6 +29,12 @@ internal class CliRunSettingsArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly IRunSettingsProvider _runSettingsProvider; + + public CliRunSettingsArgumentProcessor(IRunSettingsProvider runSettingsProvider) + { + _runSettingsProvider = runSettingsProvider; + } /// /// Gets the metadata. @@ -43,7 +49,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new CliRunSettingsArgumentExecutor(RunSettingsManager.Instance, CommandLineOptions.Instance)); + new CliRunSettingsArgumentExecutor(_runSettingsProvider, CommandLineOptions.Instance)); set => _executor = value; } diff --git a/src/vstest.console/Processors/CollectArgumentProcessor.cs b/src/vstest.console/Processors/CollectArgumentProcessor.cs index c050e85f22..c4d42df03f 100644 --- a/src/vstest.console/Processors/CollectArgumentProcessor.cs +++ b/src/vstest.console/Processors/CollectArgumentProcessor.cs @@ -35,6 +35,12 @@ internal class CollectArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly IRunSettingsProvider _runSettingsProvider; + + public CollectArgumentProcessor(IRunSettingsProvider runSettingsProvider) + { + _runSettingsProvider = runSettingsProvider; + } /// /// Gets the metadata. @@ -49,7 +55,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new CollectArgumentExecutor(RunSettingsManager.Instance, new FileHelper())); + new CollectArgumentExecutor(_runSettingsProvider, new FileHelper())); set => _executor = value; } diff --git a/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs b/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs index 57342e1c06..6271fd210e 100644 --- a/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs +++ b/src/vstest.console/Processors/EnableBlameArgumentProcessor.cs @@ -40,12 +40,14 @@ internal class EnableBlameArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly IRunSettingsProvider _runSettingsProvider; /// /// Initializes a new instance of the class. /// - public EnableBlameArgumentProcessor() + public EnableBlameArgumentProcessor(IRunSettingsProvider runSettingsProvider) { + _runSettingsProvider = runSettingsProvider; } public Lazy Metadata @@ -58,7 +60,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new EnableBlameArgumentExecutor(RunSettingsManager.Instance, new PlatformEnvironment(), new FileHelper())); + new EnableBlameArgumentExecutor(_runSettingsProvider, new PlatformEnvironment(), new FileHelper())); set => _executor = value; } diff --git a/src/vstest.console/Processors/EnableCodeCoverageArgumentProcessor.cs b/src/vstest.console/Processors/EnableCodeCoverageArgumentProcessor.cs index f026d9e12c..861d14acfb 100644 --- a/src/vstest.console/Processors/EnableCodeCoverageArgumentProcessor.cs +++ b/src/vstest.console/Processors/EnableCodeCoverageArgumentProcessor.cs @@ -29,6 +29,12 @@ internal class EnableCodeCoverageArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly IRunSettingsProvider _runSettingsProvider; + + public EnableCodeCoverageArgumentProcessor(IRunSettingsProvider runSettingsProvider) + { + _runSettingsProvider = runSettingsProvider; + } /// /// Gets the metadata. @@ -43,7 +49,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new EnableCodeCoverageArgumentExecutor(CommandLineOptions.Instance, RunSettingsManager.Instance, new FileHelper())); + new EnableCodeCoverageArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider, new FileHelper())); set => _executor = value; } diff --git a/src/vstest.console/Processors/EnableLoggerArgumentProcessor.cs b/src/vstest.console/Processors/EnableLoggerArgumentProcessor.cs index 6cdc279818..2533057de0 100644 --- a/src/vstest.console/Processors/EnableLoggerArgumentProcessor.cs +++ b/src/vstest.console/Processors/EnableLoggerArgumentProcessor.cs @@ -27,6 +27,12 @@ internal class EnableLoggerArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly IRunSettingsProvider _runSettingsProvider; + + public EnableLoggerArgumentProcessor(IRunSettingsProvider runSettingsProvider) + { + _runSettingsProvider = runSettingsProvider; + } /// /// Gets or sets the executor. @@ -34,7 +40,7 @@ internal class EnableLoggerArgumentProcessor : IArgumentProcessor public Lazy? Executor { get => _executor ??= new Lazy(() => - new EnableLoggerArgumentExecutor(RunSettingsManager.Instance)); + new EnableLoggerArgumentExecutor(_runSettingsProvider)); set => _executor = value; } diff --git a/src/vstest.console/Processors/EnvironmentArgumentProcessor.cs b/src/vstest.console/Processors/EnvironmentArgumentProcessor.cs index c38bd87461..deb175719a 100644 --- a/src/vstest.console/Processors/EnvironmentArgumentProcessor.cs +++ b/src/vstest.console/Processors/EnvironmentArgumentProcessor.cs @@ -28,11 +28,17 @@ internal class EnvironmentArgumentProcessor : IArgumentProcessor public const string CommandName = "/Environment"; private Lazy? _metadata; private Lazy? _executor; + private readonly IRunSettingsProvider _runSettingsProvider; + + public EnvironmentArgumentProcessor(IRunSettingsProvider runSettingsProvider) + { + _runSettingsProvider = runSettingsProvider; + } public Lazy? Executor { get => _executor ??= new Lazy(() => - new ArgumentExecutor(CommandLineOptions.Instance, RunSettingsManager.Instance, ConsoleOutput.Instance)); + new ArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider, ConsoleOutput.Instance)); set => _executor = value; } diff --git a/src/vstest.console/Processors/FrameworkArgumentProcessor.cs b/src/vstest.console/Processors/FrameworkArgumentProcessor.cs index b2bdd90a31..a8c11f4818 100644 --- a/src/vstest.console/Processors/FrameworkArgumentProcessor.cs +++ b/src/vstest.console/Processors/FrameworkArgumentProcessor.cs @@ -27,6 +27,12 @@ internal class FrameworkArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly IRunSettingsProvider _runSettingsProvider; + + public FrameworkArgumentProcessor(IRunSettingsProvider runSettingsProvider) + { + _runSettingsProvider = runSettingsProvider; + } /// /// Gets the metadata. @@ -41,7 +47,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new FrameworkArgumentExecutor(CommandLineOptions.Instance, RunSettingsManager.Instance)); + new FrameworkArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider)); set => _executor = value; } diff --git a/src/vstest.console/Processors/InIsolationArgumentProcessor.cs b/src/vstest.console/Processors/InIsolationArgumentProcessor.cs index 0666f0e8d5..55bef0523e 100644 --- a/src/vstest.console/Processors/InIsolationArgumentProcessor.cs +++ b/src/vstest.console/Processors/InIsolationArgumentProcessor.cs @@ -23,6 +23,12 @@ internal class InIsolationArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly IRunSettingsProvider _runSettingsProvider; + + public InIsolationArgumentProcessor(IRunSettingsProvider runSettingsProvider) + { + _runSettingsProvider = runSettingsProvider; + } /// /// Gets the metadata. @@ -37,7 +43,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new InIsolationArgumentExecutor(CommandLineOptions.Instance, RunSettingsManager.Instance)); + new InIsolationArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider)); set => _executor = value; } diff --git a/src/vstest.console/Processors/ListFullyQualifiedTestsArgumentProcessor.cs b/src/vstest.console/Processors/ListFullyQualifiedTestsArgumentProcessor.cs index 8bd9581d09..7530d98028 100644 --- a/src/vstest.console/Processors/ListFullyQualifiedTestsArgumentProcessor.cs +++ b/src/vstest.console/Processors/ListFullyQualifiedTestsArgumentProcessor.cs @@ -32,6 +32,12 @@ internal class ListFullyQualifiedTestsArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly IRunSettingsProvider _runSettingsProvider; + + public ListFullyQualifiedTestsArgumentProcessor(IRunSettingsProvider runSettingsProvider) + { + _runSettingsProvider = runSettingsProvider; + } /// /// Gets the metadata. @@ -48,7 +54,7 @@ public Lazy? Executor get => _executor ??= new Lazy(() => new ListFullyQualifiedTestsArgumentExecutor( CommandLineOptions.Instance, - RunSettingsManager.Instance, + _runSettingsProvider, TestRequestManager.Instance)); set => _executor = value; diff --git a/src/vstest.console/Processors/ListTestsArgumentProcessor.cs b/src/vstest.console/Processors/ListTestsArgumentProcessor.cs index 18dcba4914..3165c5f1e6 100644 --- a/src/vstest.console/Processors/ListTestsArgumentProcessor.cs +++ b/src/vstest.console/Processors/ListTestsArgumentProcessor.cs @@ -35,6 +35,12 @@ internal class ListTestsArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly IRunSettingsProvider _runSettingsProvider; + + public ListTestsArgumentProcessor(IRunSettingsProvider runSettingsProvider) + { + _runSettingsProvider = runSettingsProvider; + } /// /// Gets the metadata. @@ -51,7 +57,7 @@ public Lazy? Executor get => _executor ??= new Lazy(() => new ListTestsArgumentExecutor( CommandLineOptions.Instance, - RunSettingsManager.Instance, + _runSettingsProvider, TestRequestManager.Instance)); set => _executor = value; diff --git a/src/vstest.console/Processors/ParallelArgumentProcessor.cs b/src/vstest.console/Processors/ParallelArgumentProcessor.cs index 3b2d880049..51ca89909a 100644 --- a/src/vstest.console/Processors/ParallelArgumentProcessor.cs +++ b/src/vstest.console/Processors/ParallelArgumentProcessor.cs @@ -22,6 +22,12 @@ internal class ParallelArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly IRunSettingsProvider _runSettingsProvider; + + public ParallelArgumentProcessor(IRunSettingsProvider runSettingsProvider) + { + _runSettingsProvider = runSettingsProvider; + } /// /// Gets the metadata. @@ -36,7 +42,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new ParallelArgumentExecutor(CommandLineOptions.Instance, RunSettingsManager.Instance)); + new ParallelArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider)); set => _executor = value; } diff --git a/src/vstest.console/Processors/PlatformArgumentProcessor.cs b/src/vstest.console/Processors/PlatformArgumentProcessor.cs index 7b4dd50c87..621d3688b3 100644 --- a/src/vstest.console/Processors/PlatformArgumentProcessor.cs +++ b/src/vstest.console/Processors/PlatformArgumentProcessor.cs @@ -28,6 +28,12 @@ internal class PlatformArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly IRunSettingsProvider _runSettingsProvider; + + public PlatformArgumentProcessor(IRunSettingsProvider runSettingsProvider) + { + _runSettingsProvider = runSettingsProvider; + } /// /// Gets the metadata. @@ -42,7 +48,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new PlatformArgumentExecutor(CommandLineOptions.Instance, RunSettingsManager.Instance)); + new PlatformArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider)); set => _executor = value; } diff --git a/src/vstest.console/Processors/ResultsDirectoryArgumentProcessor.cs b/src/vstest.console/Processors/ResultsDirectoryArgumentProcessor.cs index b7e03835fd..1b4afe42c1 100644 --- a/src/vstest.console/Processors/ResultsDirectoryArgumentProcessor.cs +++ b/src/vstest.console/Processors/ResultsDirectoryArgumentProcessor.cs @@ -27,6 +27,12 @@ internal class ResultsDirectoryArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly IRunSettingsProvider _runSettingsProvider; + + public ResultsDirectoryArgumentProcessor(IRunSettingsProvider runSettingsProvider) + { + _runSettingsProvider = runSettingsProvider; + } /// /// Gets the metadata. @@ -41,7 +47,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new ResultsDirectoryArgumentExecutor(CommandLineOptions.Instance, RunSettingsManager.Instance)); + new ResultsDirectoryArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider)); set => _executor = value; } diff --git a/src/vstest.console/Processors/RunSettingsArgumentProcessor.cs b/src/vstest.console/Processors/RunSettingsArgumentProcessor.cs index 62597c2c6a..84517a465a 100644 --- a/src/vstest.console/Processors/RunSettingsArgumentProcessor.cs +++ b/src/vstest.console/Processors/RunSettingsArgumentProcessor.cs @@ -30,6 +30,12 @@ internal class RunSettingsArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly IRunSettingsProvider _runSettingsProvider; + + public RunSettingsArgumentProcessor(IRunSettingsProvider runSettingsProvider) + { + _runSettingsProvider = runSettingsProvider; + } /// /// Gets the metadata. @@ -44,7 +50,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new RunSettingsArgumentExecutor(CommandLineOptions.Instance, RunSettingsManager.Instance)); + new RunSettingsArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider)); set => _executor = value; } diff --git a/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs b/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs index e3aba77d0a..5212748023 100644 --- a/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs +++ b/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs @@ -30,6 +30,12 @@ internal class RunSpecificTestsArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly IRunSettingsProvider _runSettingsProvider; + + public RunSpecificTestsArgumentProcessor(IRunSettingsProvider runSettingsProvider) + { + _runSettingsProvider = runSettingsProvider; + } public Lazy Metadata => _metadata ??= new Lazy(() => @@ -40,7 +46,7 @@ public Lazy? Executor get => _executor ??= new Lazy(() => new RunSpecificTestsArgumentExecutor( CommandLineOptions.Instance, - RunSettingsManager.Instance, + _runSettingsProvider, TestRequestManager.Instance, new ArtifactProcessingManager(CommandLineOptions.Instance.TestSessionCorrelationId), ConsoleOutput.Instance)); @@ -143,7 +149,7 @@ public RunSpecificTestsArgumentExecutor( _runSettingsManager = runSettingsProvider; Output = output; _discoveryEventsRegistrar = new DiscoveryEventsRegistrar(DiscoveryRequest_OnDiscoveredTests); - _testRunEventsRegistrar = new TestRunRequestEventsRegistrar(Output, _commandLineOptions, artifactProcessingManager); + _testRunEventsRegistrar = new TestRunRequestEventsRegistrar(Output, _commandLineOptions, artifactProcessingManager, _runSettingsManager); } #region IArgumentProcessor @@ -325,12 +331,14 @@ private class TestRunRequestEventsRegistrar : ITestRunEventsRegistrar private readonly IOutput _output; private readonly CommandLineOptions _commandLineOptions; private readonly IArtifactProcessingManager _artifactProcessingManager; + private readonly IRunSettingsProvider _runSettingsProvider; - public TestRunRequestEventsRegistrar(IOutput output, CommandLineOptions commandLineOptions, IArtifactProcessingManager artifactProcessingManager) + public TestRunRequestEventsRegistrar(IOutput output, CommandLineOptions commandLineOptions, IArtifactProcessingManager artifactProcessingManager, IRunSettingsProvider runSettingsProvider) { _output = output; _commandLineOptions = commandLineOptions; _artifactProcessingManager = artifactProcessingManager; + _runSettingsProvider = runSettingsProvider; } public void LogWarning(string message) @@ -371,8 +379,9 @@ private void TestRunRequest_OnRunCompletion(object? sender, TestRunCompleteEvent // Collect tests session artifacts for post processing if (_commandLineOptions.ArtifactProcessingMode == ArtifactProcessingMode.Collect) { - TPDebug.Assert(RunSettingsManager.Instance.ActiveRunSettings.SettingsXml is not null, "RunSettingsManager.Instance.ActiveRunSettings.SettingsXml is null"); - _artifactProcessingManager.CollectArtifacts(e, RunSettingsManager.Instance.ActiveRunSettings.SettingsXml); + var settingsXml = _runSettingsProvider.ActiveRunSettings?.SettingsXml; + TPDebug.Assert(settingsXml is not null, "RunSettingsProvider.ActiveRunSettings.SettingsXml is null"); + _artifactProcessingManager.CollectArtifacts(e, settingsXml); } } } diff --git a/src/vstest.console/Processors/RunTestsArgumentProcessor.cs b/src/vstest.console/Processors/RunTestsArgumentProcessor.cs index 2ac81b3d48..110ab00316 100644 --- a/src/vstest.console/Processors/RunTestsArgumentProcessor.cs +++ b/src/vstest.console/Processors/RunTestsArgumentProcessor.cs @@ -26,6 +26,12 @@ internal class RunTestsArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly IRunSettingsProvider _runSettingsProvider; + + public RunTestsArgumentProcessor(IRunSettingsProvider runSettingsProvider) + { + _runSettingsProvider = runSettingsProvider; + } public Lazy Metadata => _metadata ??= new Lazy(() => @@ -36,7 +42,7 @@ public Lazy? Executor get => _executor ??= new Lazy(() => new RunTestsArgumentExecutor( CommandLineOptions.Instance, - RunSettingsManager.Instance, + _runSettingsProvider, TestRequestManager.Instance, new ArtifactProcessingManager(CommandLineOptions.Instance.TestSessionCorrelationId), ConsoleOutput.Instance)); @@ -112,7 +118,7 @@ public RunTestsArgumentExecutor( _runSettingsManager = runSettingsProvider; _testRequestManager = testRequestManager; Output = output; - _testRunEventsRegistrar = new TestRunRequestEventsRegistrar(Output, _commandLineOptions, artifactProcessingManager); + _testRunEventsRegistrar = new TestRunRequestEventsRegistrar(Output, _commandLineOptions, artifactProcessingManager, _runSettingsManager); } public void Initialize(string? argument) @@ -184,12 +190,14 @@ private class TestRunRequestEventsRegistrar : ITestRunEventsRegistrar private readonly IOutput _output; private readonly CommandLineOptions _commandLineOptions; private readonly IArtifactProcessingManager _artifactProcessingManager; + private readonly IRunSettingsProvider _runSettingsProvider; - public TestRunRequestEventsRegistrar(IOutput output, CommandLineOptions commandLineOptions, IArtifactProcessingManager artifactProcessingManager) + public TestRunRequestEventsRegistrar(IOutput output, CommandLineOptions commandLineOptions, IArtifactProcessingManager artifactProcessingManager, IRunSettingsProvider runSettingsProvider) { _output = output; _commandLineOptions = commandLineOptions; _artifactProcessingManager = artifactProcessingManager; + _runSettingsProvider = runSettingsProvider; } public void LogWarning(string message) @@ -231,8 +239,9 @@ private void TestRunRequest_OnRunCompletion(object? sender, TestRunCompleteEvent // Collect tests session artifacts for post processing if (_commandLineOptions.ArtifactProcessingMode == ArtifactProcessingMode.Collect) { - TPDebug.Assert(RunSettingsManager.Instance.ActiveRunSettings.SettingsXml is not null, "RunSettingsManager.Instance.ActiveRunSettings.SettingsXml is null"); - _artifactProcessingManager.CollectArtifacts(e, RunSettingsManager.Instance.ActiveRunSettings.SettingsXml); + var settingsXml = _runSettingsProvider.ActiveRunSettings?.SettingsXml; + TPDebug.Assert(settingsXml is not null, "RunSettingsProvider.ActiveRunSettings.SettingsXml is null"); + _artifactProcessingManager.CollectArtifacts(e, settingsXml); } } } diff --git a/src/vstest.console/Processors/TestAdapterLoadingStrategyArgumentProcessor.cs b/src/vstest.console/Processors/TestAdapterLoadingStrategyArgumentProcessor.cs index 91ee48d550..8cd8171be5 100644 --- a/src/vstest.console/Processors/TestAdapterLoadingStrategyArgumentProcessor.cs +++ b/src/vstest.console/Processors/TestAdapterLoadingStrategyArgumentProcessor.cs @@ -29,6 +29,12 @@ internal class TestAdapterLoadingStrategyArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly IRunSettingsProvider _runSettingsProvider; + + public TestAdapterLoadingStrategyArgumentProcessor(IRunSettingsProvider runSettingsProvider) + { + _runSettingsProvider = runSettingsProvider; + } /// /// Gets the metadata. @@ -43,7 +49,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new TestAdapterLoadingStrategyArgumentExecutor(CommandLineOptions.Instance, RunSettingsManager.Instance, ConsoleOutput.Instance, new FileHelper())); + new TestAdapterLoadingStrategyArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider, ConsoleOutput.Instance, new FileHelper())); set => _executor = value; } diff --git a/src/vstest.console/Processors/TestAdapterPathArgumentProcessor.cs b/src/vstest.console/Processors/TestAdapterPathArgumentProcessor.cs index 8ae3214634..a8ff533592 100644 --- a/src/vstest.console/Processors/TestAdapterPathArgumentProcessor.cs +++ b/src/vstest.console/Processors/TestAdapterPathArgumentProcessor.cs @@ -29,6 +29,12 @@ internal class TestAdapterPathArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly IRunSettingsProvider _runSettingsProvider; + + public TestAdapterPathArgumentProcessor(IRunSettingsProvider runSettingsProvider) + { + _runSettingsProvider = runSettingsProvider; + } /// /// Gets the metadata. @@ -43,7 +49,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new TestAdapterPathArgumentExecutor(CommandLineOptions.Instance, RunSettingsManager.Instance, + new TestAdapterPathArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider, ConsoleOutput.Instance, new FileHelper())); set => _executor = value; diff --git a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs index 81f6123af8..d05037f287 100644 --- a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs +++ b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs @@ -7,6 +7,8 @@ using System.Diagnostics.Contracts; using System.Linq; +using Microsoft.VisualStudio.TestPlatform.Common; +using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.Utilities; @@ -44,10 +46,16 @@ protected ArgumentProcessorFactory(IEnumerable argumentProce /// /// The feature flag support. /// + /// + /// The run settings provider that the created argument processors read from and write to. + /// Defaults to the ambient when not provided, so that + /// callers (and the composition root) can inject an isolated instance instead of sharing static state. + /// /// ArgumentProcessorFactory. - internal static ArgumentProcessorFactory Create(IFeatureFlag? featureFlag = null) + internal static ArgumentProcessorFactory Create(IFeatureFlag? featureFlag = null, IRunSettingsProvider? runSettingsProvider = null) { - var defaultArgumentProcessor = DefaultArgumentProcessors; + runSettingsProvider ??= RunSettingsManager.Instance; + var defaultArgumentProcessor = GetDefaultArgumentProcessors(runSettingsProvider); if (!(featureFlag ?? FeatureFlag.Instance).IsSet(FeatureFlag.VSTEST_DISABLE_ARTIFACTS_POSTPROCESSING)) { @@ -181,41 +189,41 @@ public IEnumerable GetArgumentProcessorsToAlwaysExecute() .Where(lazyProcessor => lazyProcessor.Metadata.Value.IsSpecialCommand && lazyProcessor.Metadata.Value.AlwaysExecute); } - private static IList DefaultArgumentProcessors => new List { + private static IList GetDefaultArgumentProcessors(IRunSettingsProvider runSettingsProvider) => new List { new HelpArgumentProcessor(), new TestSourceArgumentProcessor(), - new ListTestsArgumentProcessor(), - new RunTestsArgumentProcessor(), - new RunSpecificTestsArgumentProcessor(), - new TestAdapterPathArgumentProcessor(), - new TestAdapterLoadingStrategyArgumentProcessor(), + new ListTestsArgumentProcessor(runSettingsProvider), + new RunTestsArgumentProcessor(runSettingsProvider), + new RunSpecificTestsArgumentProcessor(runSettingsProvider), + new TestAdapterPathArgumentProcessor(runSettingsProvider), + new TestAdapterLoadingStrategyArgumentProcessor(runSettingsProvider), new TestCaseFilterArgumentProcessor(), new ParentProcessIdArgumentProcessor(), new PortArgumentProcessor(), - new RunSettingsArgumentProcessor(), - new PlatformArgumentProcessor(), - new FrameworkArgumentProcessor(), - new EnableLoggerArgumentProcessor(), - new ParallelArgumentProcessor(), + new RunSettingsArgumentProcessor(runSettingsProvider), + new PlatformArgumentProcessor(runSettingsProvider), + new FrameworkArgumentProcessor(runSettingsProvider), + new EnableLoggerArgumentProcessor(runSettingsProvider), + new ParallelArgumentProcessor(runSettingsProvider), new EnableDiagArgumentProcessor(), - new CliRunSettingsArgumentProcessor(), - new ResultsDirectoryArgumentProcessor(), - new InIsolationArgumentProcessor(), - new CollectArgumentProcessor(), - new EnableCodeCoverageArgumentProcessor(), + new CliRunSettingsArgumentProcessor(runSettingsProvider), + new ResultsDirectoryArgumentProcessor(runSettingsProvider), + new InIsolationArgumentProcessor(runSettingsProvider), + new CollectArgumentProcessor(runSettingsProvider), + new EnableCodeCoverageArgumentProcessor(runSettingsProvider), new DisableAutoFakesArgumentProcessor(), new ResponseFileArgumentProcessor(), - new EnableBlameArgumentProcessor(), + new EnableBlameArgumentProcessor(runSettingsProvider), new AeDebuggerArgumentProcessor(), new UseVsixExtensionsArgumentProcessor(), new ListDiscoverersArgumentProcessor(), new ListExecutorsArgumentProcessor(), new ListLoggersArgumentProcessor(), new ListSettingsProvidersArgumentProcessor(), - new ListFullyQualifiedTestsArgumentProcessor(), + new ListFullyQualifiedTestsArgumentProcessor(runSettingsProvider), new ListTestsTargetPathArgumentProcessor(), new ShowDeprecateDotnetVStestMessageArgumentProcessor(), - new EnvironmentArgumentProcessor() + new EnvironmentArgumentProcessor(runSettingsProvider) }; /// diff --git a/test/vstest.console.UnitTests/Processors/CLIRunSettingsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/CLIRunSettingsArgumentProcessorTests.cs index 181517cfa5..08dc751cd2 100644 --- a/test/vstest.console.UnitTests/Processors/CLIRunSettingsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/CLIRunSettingsArgumentProcessorTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; @@ -68,14 +68,14 @@ public void Cleanup() [TestMethod] public void GetMetadataShouldReturnRunSettingsArgumentProcessorCapabilities() { - var processor = new CliRunSettingsArgumentProcessor(); + var processor = new CliRunSettingsArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is CliRunSettingsArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnRunSettingsArgumentProcessorCapabilities() { - var processor = new CliRunSettingsArgumentProcessor(); + var processor = new CliRunSettingsArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is CliRunSettingsArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/CollectArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/CollectArgumentProcessorTests.cs index 57f2d0afce..37cbb8d6ac 100644 --- a/test/vstest.console.UnitTests/Processors/CollectArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/CollectArgumentProcessorTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; @@ -46,14 +46,14 @@ public CollectArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnCollectArgumentProcessorCapabilities() { - var processor = new CollectArgumentProcessor(); + var processor = new CollectArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is CollectArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnCollectArgumentProcessorCapabilities() { - var processor = new CollectArgumentProcessor(); + var processor = new CollectArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is CollectArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs index 4e9ebdb118..d3147dddac 100644 --- a/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/EnableBlameArgumentProcessorTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; @@ -49,14 +49,14 @@ public EnableBlameArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnEnableBlameArgumentProcessorCapabilities() { - var processor = new EnableBlameArgumentProcessor(); + var processor = new EnableBlameArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is EnableBlameArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnEnableBlameArgumentProcessorCapabilities() { - var processor = new EnableBlameArgumentProcessor(); + var processor = new EnableBlameArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is EnableBlameArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/EnableCodeCoverageArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/EnableCodeCoverageArgumentProcessorTests.cs index a541cbdcc6..3eca670101 100644 --- a/test/vstest.console.UnitTests/Processors/EnableCodeCoverageArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/EnableCodeCoverageArgumentProcessorTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; @@ -40,14 +40,14 @@ public EnableCodeCoverageArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnEnableCodeCoverageArgumentProcessorCapabilities() { - var processor = new EnableCodeCoverageArgumentProcessor(); + var processor = new EnableCodeCoverageArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is EnableCodeCoverageArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnEnableCodeCoverageArgumentProcessorCapabilities() { - var processor = new EnableCodeCoverageArgumentProcessor(); + var processor = new EnableCodeCoverageArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is EnableCodeCoverageArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/EnableLoggersArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/EnableLoggersArgumentProcessorTests.cs index 8555b3cca7..1de1424ac6 100644 --- a/test/vstest.console.UnitTests/Processors/EnableLoggersArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/EnableLoggersArgumentProcessorTests.cs @@ -8,6 +8,8 @@ using Microsoft.VisualStudio.TestPlatform.Common; using Microsoft.VisualStudio.TestTools.UnitTesting; +using vstest.console.UnitTests.Processors; + using CommandLineResources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources.Resources; namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; @@ -33,14 +35,14 @@ public void Cleanup() [TestMethod] public void GetMetadataShouldReturnEnableLoggerArgumentProcessorCapabilities() { - EnableLoggerArgumentProcessor processor = new(); + EnableLoggerArgumentProcessor processor = new(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is EnableLoggerArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnEnableLoggerArgumentExecutor() { - EnableLoggerArgumentProcessor processor = new(); + EnableLoggerArgumentProcessor processor = new(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is EnableLoggerArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/FrameworkArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/FrameworkArgumentProcessorTests.cs index 35a7c8396e..6e2a05fad0 100644 --- a/test/vstest.console.UnitTests/Processors/FrameworkArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/FrameworkArgumentProcessorTests.cs @@ -32,14 +32,14 @@ public void TestCleanup() [TestMethod] public void GetMetadataShouldReturnFrameworkArgumentProcessorCapabilities() { - var processor = new FrameworkArgumentProcessor(); + var processor = new FrameworkArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is FrameworkArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnFrameworkArgumentExecutor() { - var processor = new FrameworkArgumentProcessor(); + var processor = new FrameworkArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is FrameworkArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/InIsolationArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/InIsolationArgumentProcessorTests.cs index 49a4f07346..f512082204 100644 --- a/test/vstest.console.UnitTests/Processors/InIsolationArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/InIsolationArgumentProcessorTests.cs @@ -32,21 +32,21 @@ public void TestCleanup() [TestMethod] public void GetMetadataShouldReturnInProcessArgumentProcessorCapabilities() { - var processor = new InIsolationArgumentProcessor(); + var processor = new InIsolationArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is InIsolationArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnInProcessArgumentExecutor() { - var processor = new InIsolationArgumentProcessor(); + var processor = new InIsolationArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is InIsolationArgumentExecutor); } [TestMethod] public void InIsolationArgumentProcessorMetadataShouldProvideAppropriateCapabilities() { - var isolationProcessor = new InIsolationArgumentProcessor(); + var isolationProcessor = new InIsolationArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsFalse(isolationProcessor.Metadata.Value.AllowMultiple); Assert.IsFalse(isolationProcessor.Metadata.Value.AlwaysExecute); Assert.IsFalse(isolationProcessor.Metadata.Value.IsAction); diff --git a/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs index 83159cdf58..a01c48e419 100644 --- a/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs @@ -95,8 +95,8 @@ public ListFullyQualifiedTestsArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnListFullyQualifiedTestsArgumentProcessorCapabilities() { - var processor = new ListTestsArgumentProcessor(); - Assert.IsTrue(processor.Metadata.Value is ListTestsArgumentProcessorCapabilities); + var processor = new ListFullyQualifiedTestsArgumentProcessor(new TestableRunSettingsProvider()); + Assert.IsTrue(processor.Metadata.Value is ListFullyQualifiedTestsArgumentProcessorCapabilities); } /// @@ -105,7 +105,7 @@ public void GetMetadataShouldReturnListFullyQualifiedTestsArgumentProcessorCapab [TestMethod] public void GetExecuterShouldReturnListFullyQualifiedTestsArgumentProcessorCapabilities() { - var processor = new ListFullyQualifiedTestsArgumentProcessor(); + var processor = new ListFullyQualifiedTestsArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is ListFullyQualifiedTestsArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs index fad84601b1..1f7263f837 100644 --- a/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs @@ -93,7 +93,7 @@ public ListTestsArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnListTestsArgumentProcessorCapabilities() { - var processor = new ListTestsArgumentProcessor(); + var processor = new ListTestsArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is ListTestsArgumentProcessorCapabilities); } @@ -103,7 +103,7 @@ public void GetMetadataShouldReturnListTestsArgumentProcessorCapabilities() [TestMethod] public void GetExecuterShouldReturnListTestsArgumentProcessorCapabilities() { - var processor = new ListTestsArgumentProcessor(); + var processor = new ListTestsArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is ListTestsArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/ParallelArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ParallelArgumentProcessorTests.cs index 4d264812a5..227e0297d5 100644 --- a/test/vstest.console.UnitTests/Processors/ParallelArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ParallelArgumentProcessorTests.cs @@ -29,14 +29,14 @@ public void TestCleanup() [TestMethod] public void GetMetadataShouldReturnParallelArgumentProcessorCapabilities() { - var processor = new ParallelArgumentProcessor(); + var processor = new ParallelArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is ParallelArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnParallelArgumentExecutor() { - var processor = new ParallelArgumentProcessor(); + var processor = new ParallelArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is ParallelArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs index 73f38ca8cd..ae9ccacd08 100644 --- a/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs @@ -32,14 +32,14 @@ public void TestCleanup() [TestMethod] public void GetMetadataShouldReturnPlatformArgumentProcessorCapabilities() { - var processor = new PlatformArgumentProcessor(); + var processor = new PlatformArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is PlatformArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnPlatformArgumentExecutor() { - var processor = new PlatformArgumentProcessor(); + var processor = new PlatformArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is PlatformArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/ResultsDirectoryArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ResultsDirectoryArgumentProcessorTests.cs index c1c9cdb2c9..bca6d8c828 100644 --- a/test/vstest.console.UnitTests/Processors/ResultsDirectoryArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ResultsDirectoryArgumentProcessorTests.cs @@ -34,14 +34,14 @@ public void TestCleanup() [TestMethod] public void GetMetadataShouldReturnResultsDirectoryArgumentProcessorCapabilities() { - var processor = new ResultsDirectoryArgumentProcessor(); + var processor = new ResultsDirectoryArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is ResultsDirectoryArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnResultsDirectoryArgumentExecutor() { - var processor = new ResultsDirectoryArgumentProcessor(); + var processor = new ResultsDirectoryArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is ResultsDirectoryArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessorTests.cs index be82e2687b..535b6877e4 100644 --- a/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessorTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; @@ -40,14 +40,14 @@ public void TestCleanup() [TestMethod] public void GetMetadataShouldReturnRunSettingsArgumentProcessorCapabilities() { - var processor = new RunSettingsArgumentProcessor(); + var processor = new RunSettingsArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is RunSettingsArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnRunSettingsArgumentExecutor() { - var processor = new RunSettingsArgumentProcessor(); + var processor = new RunSettingsArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is RunSettingsArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs index 6462c6a940..6a8b1e3c0a 100644 --- a/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs @@ -81,7 +81,7 @@ public RunSpecificTestsArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnRunSpecificTestsArgumentProcessorCapabilities() { - RunSpecificTestsArgumentProcessor processor = new(); + RunSpecificTestsArgumentProcessor processor = new(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is RunSpecificTestsArgumentProcessorCapabilities); } @@ -89,7 +89,7 @@ public void GetMetadataShouldReturnRunSpecificTestsArgumentProcessorCapabilities [TestMethod] public void GetExecutorShouldReturnRunSpecificTestsArgumentExecutor() { - RunSpecificTestsArgumentProcessor processor = new(); + RunSpecificTestsArgumentProcessor processor = new(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is RunSpecificTestsArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs index bb540babf1..bb0a2b92f1 100644 --- a/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs @@ -79,14 +79,14 @@ public RunTestsArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnRunTestsArgumentProcessorCapabilities() { - RunTestsArgumentProcessor processor = new(); + RunTestsArgumentProcessor processor = new(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is RunTestsArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnRunTestsArgumentProcessorCapabilities() { - RunTestsArgumentProcessor processor = new(); + RunTestsArgumentProcessor processor = new(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is RunTestsArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs index dbaa81af15..8d28fd0016 100644 --- a/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs @@ -17,6 +17,8 @@ using Moq; +using vstest.console.UnitTests.Processors; + namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] @@ -41,14 +43,14 @@ public void TestClean() [TestMethod] public void GetMetadataShouldReturnTestAdapterPathArgumentProcessorCapabilities() { - var processor = new TestAdapterPathArgumentProcessor(); + var processor = new TestAdapterPathArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is TestAdapterPathArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnTestAdapterPathArgumentProcessorCapabilities() { - var processor = new TestAdapterPathArgumentProcessor(); + var processor = new TestAdapterPathArgumentProcessor(new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is TestAdapterPathArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs b/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs index 48f971b4a3..3ce52e3728 100644 --- a/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs +++ b/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs @@ -6,11 +6,14 @@ using System.Linq; using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; +using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; using Microsoft.VisualStudio.TestPlatform.Utilities; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using vstest.console.UnitTests.Processors; + namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors.Utilities; [TestClass] @@ -164,7 +167,11 @@ private static IEnumerable GetArgumentProcessors(bool specia foreach (var processor in allProcessors) { - var instance = Activator.CreateInstance(processor) as IArgumentProcessor; + // Some processors require an IRunSettingsProvider via constructor injection; the rest are parameterless. + var runSettingsCtor = processor.GetConstructor([typeof(IRunSettingsProvider)]); + var instance = (runSettingsCtor is not null + ? runSettingsCtor.Invoke([new TestableRunSettingsProvider()]) + : Activator.CreateInstance(processor)) as IArgumentProcessor; Assert.IsNotNull(instance, $"Unable to instantiate processor: {processor}"); var specialProcessor = instance.Metadata.Value.IsSpecialCommand; From c560fc4d30258bfb56b248ecc7c48e623cbcb01c Mon Sep 17 00:00:00 2001 From: dotnet bot Date: Fri, 3 Jul 2026 04:11:47 -0700 Subject: [PATCH 13/87] Localized file check-in by OneLocBuild Task: Build definition ID 1222: Build ID 3013854 (#16204) --- .../Resources/xlf/Resources.fr.xlf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.fr.xlf b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.fr.xlf index 8027a007c6..0fa857a2fb 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.fr.xlf +++ b/src/Microsoft.TestPlatform.TestHostProvider/Resources/xlf/Resources.fr.xlf @@ -30,9 +30,9 @@ Vérifiez ce qui suit : Running .NET Framework tests is supported on Windows only. Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. - Running .NET Framework tests is supported on Windows only. + L’exécution de tests .NET Framework est prise en charge uniquement sur Windows. -Running .NET Framework tests on this operating system relied on Mono, which is no longer supported. To run these tests, run them on Windows, or change the test project to target .NET instead of .NET Framework. +L’exécution .NET Framework tests sur ce système d’exploitation reposait sur Mono, qui n’est plus pris en charge. Pour exécuter ces tests, exécutez-les sur Windows ou modifiez le projet de test pour cibler .NET au lieu de .NET Framework. From e7110e20a92588ea41f611992a6f639c7ad907b2 Mon Sep 17 00:00:00 2001 From: Azat Mukhametshin Date: Fri, 3 Jul 2026 13:48:58 +0200 Subject: [PATCH 14/87] Fix null-safety annotation debt: NullPathConverter, DataCollectionContext, serialization ctors, PathConverter (#16186 tasks 1-3, 5) (#16196) * Fix NullPathConverter interface contract violations (#16186) Three IPathConverter methods accepted nullable parameters but returned 'param!', silently passing null through and violating the non-null return contract. Validate the arguments with ValidateArg.NotNull so null inputs throw ArgumentNullException, consistent with the real PathConverter implementation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add tests asserting NullPathConverter throws on null input (#16186) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Resolve DataCollectionContext(TestCase?) known null SessionId bug (#16186) The DataCollectionContext(TestCase?) constructor assigned SessionId = null! with a TODO acknowledging it left a non-nullable property null, which would NullReferenceException in Equals/GetHashCode (the latter was also never computed on this path). A real session id cannot be derived from a TestCase alone, so assign the existing SessionId.Empty sentinel - matching the documented convention that an empty session signifies the session is irrelevant - and compute the hash code like the other constructors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Replace = null! in serialization constructors (#16186) AfterTestRunEndResult's private serialization ctor assigned = null! to non-nullable collection properties. Newtonsoft (whose ConstructorHandling fallback the old comment referenced) is no longer used: both the STJ AfterTestRunEndResultConverter and the net462 JsoniteConvert build via the parameterized ctor with empty-collection fallbacks. Initialize the non-nullable collections to empty in the private ctor to guarantee non-null and refresh the obsolete comment. CollectorDataEntry's internal parameterless 'For XML persistence' ctor assigned = null! to its readonly fields, but the TRX logger is write-only (no read/Load path) and the ctor had zero references, so it is removed as dead code. Added a serialization regression test asserting a payload missing AttachmentSets/Metrics still deserializes to non-null empty collections. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove redundant ! after ValidateArg.NotNull in PathConverter (#16186) ValidateArg.NotNull is annotated with [NotNull], so the compiler already treats the validated variable as non-null afterwards. The null-forgiving ! operators that immediately followed these calls were noise and have been removed. The ! on the nullable NewTestResults property is kept as it is unrelated. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove dead private serialization ctor from AfterTestRunEndResult (#16186) Following the same approach as CollectorDataEntry, remove the unused private parameterless constructor instead of just replacing its null! assignments. Both serialization paths (STJ AfterTestRunEndResultConverter and net462 JsoniteConvert) construct via the parameterized constructor, and findReferences confirms the private ctor had no callers, so it is dead code. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../DataCollection/AfterTestRunEndResult.cs | 11 ----- .../EventHandlers/NullPathConverter.cs | 20 ++++++-- .../EventHandlers/PathConverter.cs | 10 ++-- .../ObjectModel/CollectorDataEntry.cs | 13 ----- .../DataCollector/DataCollectionContext.cs | 9 ++-- ...AfterTestRunEndResultSerializationTests.cs | 35 +++++++++++++ .../NullPathConverterRegressionTests.cs | 31 ++++++++++++ .../DataCollectionContextTests.cs | 49 +++++++++++++++++++ 8 files changed, 142 insertions(+), 36 deletions(-) create mode 100644 test/Microsoft.TestPlatform.ObjectModel.UnitTests/DataCollector/DataCollectionContextTests.cs diff --git a/src/Microsoft.TestPlatform.Common/DataCollection/AfterTestRunEndResult.cs b/src/Microsoft.TestPlatform.Common/DataCollection/AfterTestRunEndResult.cs index 33b70f7344..2fbc943e57 100644 --- a/src/Microsoft.TestPlatform.Common/DataCollection/AfterTestRunEndResult.cs +++ b/src/Microsoft.TestPlatform.Common/DataCollection/AfterTestRunEndResult.cs @@ -15,17 +15,6 @@ namespace Microsoft.VisualStudio.TestPlatform.Common.DataCollection; [DataContract] public class AfterTestRunEndResult { - // We have more than one ctor for backward-compatibility reason but we don't want to add dependency on Newtosoft([JsonConstructor]) - // We want to fallback to the non-public default constructor https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_ConstructorHandling.htm during deserialization - private AfterTestRunEndResult() - { - // Forcing nulls to the differnet properties as this is only serialization ctor but - // we can guarantee non-null for the other ctors. - AttachmentSets = null!; - InvokedDataCollectors = null!; - Metrics = null!; - } - /// /// Initializes a new instance of the class. /// diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/NullPathConverter.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/NullPathConverter.cs index 3f699402ec..9e55dd4e11 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/NullPathConverter.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/NullPathConverter.cs @@ -21,7 +21,11 @@ private NullPathConverter() { } Collection IPathConverter.UpdateAttachmentSets(Collection attachmentSets, PathConversionDirection _) => attachmentSets; - ICollection IPathConverter.UpdateAttachmentSets(ICollection? attachmentSets, PathConversionDirection _) => attachmentSets!; + ICollection IPathConverter.UpdateAttachmentSets(ICollection? attachmentSets, PathConversionDirection _) + { + ValidateArg.NotNull(attachmentSets, nameof(attachmentSets)); + return attachmentSets; + } DiscoveryCriteria IPathConverter.UpdateDiscoveryCriteria(DiscoveryCriteria discoveryCriteria, PathConversionDirection _) => discoveryCriteria; @@ -31,9 +35,17 @@ private NullPathConverter() { } TestCase IPathConverter.UpdateTestCase(TestCase testCase, PathConversionDirection _) => testCase; - IEnumerable IPathConverter.UpdateTestCases(IEnumerable? testCases, PathConversionDirection _) => testCases!; - - TestRunChangedEventArgs IPathConverter.UpdateTestRunChangedEventArgs(TestRunChangedEventArgs? testRunChangedArgs, PathConversionDirection _) => testRunChangedArgs!; + IEnumerable IPathConverter.UpdateTestCases(IEnumerable? testCases, PathConversionDirection _) + { + ValidateArg.NotNull(testCases, nameof(testCases)); + return testCases; + } + + TestRunChangedEventArgs IPathConverter.UpdateTestRunChangedEventArgs(TestRunChangedEventArgs? testRunChangedArgs, PathConversionDirection _) + { + ValidateArg.NotNull(testRunChangedArgs, nameof(testRunChangedArgs)); + return testRunChangedArgs; + } TestRunCompleteEventArgs IPathConverter.UpdateTestRunCompleteEventArgs(TestRunCompleteEventArgs testRunCompleteEventArgs, PathConversionDirection _) => testRunCompleteEventArgs; diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/PathConverter.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/PathConverter.cs index ead3481ded..eb4110751e 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/PathConverter.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/EventHandlers/PathConverter.cs @@ -86,8 +86,8 @@ public TestCase UpdateTestCase(TestCase testCase, PathConversionDirection update public IEnumerable UpdateTestCases(IEnumerable? testCases, PathConversionDirection updateDirection) { ValidateArg.NotNull(testCases, nameof(testCases)); - testCases!.ToList().ForEach(tc => UpdateTestCase(tc, updateDirection)); - return testCases!; + testCases.ToList().ForEach(tc => UpdateTestCase(tc, updateDirection)); + return testCases; } public TestRunCompleteEventArgs UpdateTestRunCompleteEventArgs(TestRunCompleteEventArgs testRunCompleteEventArgs, PathConversionDirection updateDirection) @@ -100,7 +100,7 @@ public TestRunCompleteEventArgs UpdateTestRunCompleteEventArgs(TestRunCompleteEv public TestRunChangedEventArgs UpdateTestRunChangedEventArgs(TestRunChangedEventArgs? testRunChangedArgs, PathConversionDirection updateDirection) { ValidateArg.NotNull(testRunChangedArgs, nameof(testRunChangedArgs)); - UpdateTestResults(testRunChangedArgs!.NewTestResults!, updateDirection); + UpdateTestResults(testRunChangedArgs.NewTestResults!, updateDirection); UpdateTestCases(testRunChangedArgs.ActiveTests, updateDirection); return testRunChangedArgs; } @@ -115,8 +115,8 @@ public Collection UpdateAttachmentSets(Collection public ICollection UpdateAttachmentSets(ICollection? attachmentSets, PathConversionDirection updateDirection) { ValidateArg.NotNull(attachmentSets, nameof(attachmentSets)); - attachmentSets!.ToList().ForEach(i => UpdateAttachmentSet(i, updateDirection)); - return attachmentSets!; + attachmentSets.ToList().ForEach(i => UpdateAttachmentSet(i, updateDirection)); + return attachmentSets; } private static AttachmentSet UpdateAttachmentSet(AttachmentSet attachmentSet, PathConversionDirection updateDirection) diff --git a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/CollectorDataEntry.cs b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/CollectorDataEntry.cs index 33b1b1dfb3..eecfbf6ff6 100644 --- a/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/CollectorDataEntry.cs +++ b/src/Microsoft.TestPlatform.Extensions.TrxLogger/ObjectModel/CollectorDataEntry.cs @@ -77,19 +77,6 @@ public CollectorDataEntry(Uri uri, string collectorDisplayName, string agentName _agentName = agentName.Trim(); } - /// - /// Initializes a new instance of the class. - /// - /// - /// For XML persistence - /// - internal CollectorDataEntry() - { - _agentName = null!; - _uri = null!; - _collectorDisplayName = null!; - } - /// /// Gets the read-only list of data attachments /// diff --git a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionContext.cs b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionContext.cs index 495fe298c8..2bffb4923d 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionContext.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/DataCollector/DataCollectionContext.cs @@ -36,9 +36,12 @@ public class DataCollectionContext public DataCollectionContext(TestCase? testCase) { TestCase = testCase; - // TODO: Comment says this ctor should never have been made public but it was added. - // This leaves a path where SessionId is null but the rest of the class doesn't handle it. - SessionId = null!; + // There is no session associated with an in-process data collection context that is + // created from a test case alone, so we use the empty session id sentinel to signify + // that the session is irrelevant in this context (matching the convention used by the + // test case and session event args). + SessionId = SessionId.Empty; + _hashCode = ComputeHashCode(); } /// diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/AfterTestRunEndResultSerializationTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/AfterTestRunEndResultSerializationTests.cs index 3a1a2bcb49..bfee478a40 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/AfterTestRunEndResultSerializationTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/AfterTestRunEndResultSerializationTests.cs @@ -205,6 +205,41 @@ public void RoundTrip(int version) Assert.AreEqual("Code Coverage", result.InvokedDataCollectors[0].FriendlyName); } + // ── Missing collections ────────────────────────────────────────────── + + [TestMethod] + [DataRow(1)] + [DataRow(7)] + public void DeserializePayloadWithMissingCollectionsReturnsNonNullEmptyCollections(int version) + { + // Regression test for https://github.com/microsoft/vstest/issues/16186 (Task 3). + // A payload that omits AttachmentSets/Metrics must still deserialize to non-null + // collections so consumers never observe null on these non-nullable properties. + var json = version == 1 + ? """ + { + "MessageType": "DataCollection.AfterTestRunEndResult", + "Payload": {} + } + """ + : """ + { + "Version": 7, + "MessageType": "DataCollection.AfterTestRunEndResult", + "Payload": {} + } + """; + + var message = JsonDataSerializer.Instance.DeserializeMessage(Minify(json)); + var result = JsonDataSerializer.Instance.DeserializePayload(message); + + Assert.IsNotNull(result); + Assert.IsNotNull(result.AttachmentSets); + Assert.IsEmpty(result.AttachmentSets); + Assert.IsNotNull(result.Metrics); + Assert.IsEmpty(result.Metrics); + } + // ── Helpers ────────────────────────────────────────────────────────── } diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/NullPathConverterRegressionTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/NullPathConverterRegressionTests.cs index 98dfe294b0..361cf322fd 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/NullPathConverterRegressionTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/EventHandlers/NullPathConverterRegressionTests.cs @@ -71,4 +71,35 @@ public void Instance_ShouldReturnSameInstance() Assert.AreSame(instance1, instance2, "NullPathConverter should be a singleton."); } + + // Regression test for #16186 — these methods previously returned param! and silently + // passed null through, violating their non-nullable return contracts. + [TestMethod] + public void UpdateAttachmentSets_Null_ShouldThrowArgumentNullException() + { + IPathConverter converter = NullPathConverter.Instance; + + Assert.ThrowsExactly( + () => converter.UpdateAttachmentSets((ICollection?)null, PathConversionDirection.Receive)); + } + + // Regression test for #16186 + [TestMethod] + public void UpdateTestCases_Null_ShouldThrowArgumentNullException() + { + IPathConverter converter = NullPathConverter.Instance; + + Assert.ThrowsExactly( + () => converter.UpdateTestCases(null, PathConversionDirection.Receive)); + } + + // Regression test for #16186 + [TestMethod] + public void UpdateTestRunChangedEventArgs_Null_ShouldThrowArgumentNullException() + { + IPathConverter converter = NullPathConverter.Instance; + + Assert.ThrowsExactly( + () => converter.UpdateTestRunChangedEventArgs(null, PathConversionDirection.Receive)); + } } diff --git a/test/Microsoft.TestPlatform.ObjectModel.UnitTests/DataCollector/DataCollectionContextTests.cs b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/DataCollector/DataCollectionContextTests.cs new file mode 100644 index 0000000000..8e10e2e1ec --- /dev/null +++ b/test/Microsoft.TestPlatform.ObjectModel.UnitTests/DataCollector/DataCollectionContextTests.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.ObjectModel.UnitTests; + +[TestClass] +public class DataCollectionContextTests +{ + // Regression test for https://github.com/microsoft/vstest/issues/16186 (Task 2). + // The DataCollectionContext(TestCase?) constructor used to assign SessionId = null!, + // leaving a non-nullable property null and breaking Equals/GetHashCode. It must now + // use the SessionId.Empty sentinel to signify that the session is irrelevant. + [TestMethod] + public void ConstructorWithTestCaseShouldSetSessionIdToEmpty() + { + var testCase = new TestCase("Test1", new System.Uri("executor://test"), @"C:\Path\test.dll"); + + var context = new DataCollectionContext(testCase); + + Assert.IsNotNull(context.SessionId); + Assert.AreEqual(SessionId.Empty, context.SessionId); + } + + [TestMethod] + public void ConstructorWithNullTestCaseShouldSetSessionIdToEmpty() + { + var context = new DataCollectionContext((TestCase?)null); + + Assert.IsNotNull(context.SessionId); + Assert.AreEqual(SessionId.Empty, context.SessionId); + } + + [TestMethod] + public void ConstructorWithTestCaseShouldProduceWorkingEqualsAndGetHashCode() + { + var testCase = new TestCase("Test1", new System.Uri("executor://test"), @"C:\Path\test.dll"); + + var context1 = new DataCollectionContext(testCase); + var context2 = new DataCollectionContext(testCase); + + // Equals dereferences SessionId, so this would throw if SessionId were null. + Assert.AreEqual(context1, context2); + Assert.AreEqual(context1.GetHashCode(), context2.GetHashCode()); + } +} From 7c991335f2fa70d51158cda9db3bd67573bc4677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Fri, 3 Jul 2026 15:52:05 +0200 Subject: [PATCH 15/87] Inject IRunSettingsHelper instead of reading RunSettingsHelper.Instance (#16205) The argument processors that write the request-scoped runsettings flags (IsDefaultTargetArchitecture, IsDesignMode) reach for RunSettingsHelper.Instance, and the readers deeper in the pipeline do the same, so those flags are shared process-wide static state that leaks across requests in design mode. This threads IRunSettingsHelper through the composition roots, defaulting to RunSettingsHelper.Instance so behavior is unchanged, mirroring the IRunSettingsProvider work in #16200. - ArgumentProcessorFactory.Create(...) takes an optional IRunSettingsHelper and threads it into the four processors that read/write the flags; Executor owns it and passes it in, defaulting to RunSettingsHelper.Instance. PortArgumentProcessor picks up injection for the first time. - TestRequestManager and DataCollectorAttachmentsProcessorsFactory take the helper through their DI constructors and read the injected instance. - DotnetTestHostManager is activated by reflection through the extension framework, not by the engine, so it stays on the .Instance fallback (same shared instance). RunSettingsHelper.Instance is not obsoleted; the remaining references are the composition-root defaults. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...taCollectorAttachmentsProcessorsFactory.cs | 10 +++++- src/vstest.console/CommandLine/Executor.cs | 13 +++++-- .../CLIRunSettingsArgumentProcessor.cs | 14 +++++--- .../Processors/PlatformArgumentProcessor.cs | 17 ++++++--- .../Processors/PortArgumentProcessor.cs | 30 +++++++++++----- .../RunSettingsArgumentProcessor.cs | 12 ++++--- .../Utilities/ArgumentProcessorFactory.cs | 22 ++++++++---- .../TestPlatformHelpers/TestRequestManager.cs | 36 +++++++++++++++++-- .../CLIRunSettingsArgumentProcessorTests.cs | 10 ++++-- .../PlatformArgumentProcessorTests.cs | 10 ++++-- .../Processors/PortArgumentProcessorTests.cs | 20 +++++++---- .../RunSettingsArgumentProcessorTests.cs | 13 +++---- .../ArgumentProcessorFactoryTests.cs | 19 +++++++--- .../TestRequestManagerTests.cs | 12 ++++--- 14 files changed, 173 insertions(+), 65 deletions(-) diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/AttachmentsProcessing/DataCollectorAttachmentsProcessorsFactory.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/AttachmentsProcessing/DataCollectorAttachmentsProcessorsFactory.cs index 335b055609..f0bf7c3909 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/AttachmentsProcessing/DataCollectorAttachmentsProcessorsFactory.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/AttachmentsProcessing/DataCollectorAttachmentsProcessorsFactory.cs @@ -15,6 +15,7 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using Microsoft.VisualStudio.TestPlatform.Utilities; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestRunAttachmentsProcessing; @@ -23,6 +24,13 @@ internal class DataCollectorAttachmentsProcessorsFactory : IDataCollectorAttachm private const string CoverageFriendlyName = "Code Coverage"; private static readonly ConcurrentDictionary DataCollectorExtensionManagerCache = new(); + private readonly IRunSettingsHelper _runSettingsHelper; + + public DataCollectorAttachmentsProcessorsFactory(IRunSettingsHelper? runSettingsHelper = null) + { + _runSettingsHelper = runSettingsHelper ?? RunSettingsHelper.Instance; + } + public DataCollectorAttachmentProcessor[] Create(InvokedDataCollector[]? invokedDataCollectors, IMessageLogger? logger) { IDictionary> datacollectorsAttachmentsProcessors = new Dictionary>(); @@ -52,7 +60,7 @@ public DataCollectorAttachmentProcessor[] Create(InvokedDataCollector[]? invoked #endif // If we're in design mode we need to load the extension inside a different AppDomain to avoid to lock extension file containers. - if (canUseAppDomains && RunSettingsHelper.Instance.IsDesignMode) + if (canUseAppDomains && _runSettingsHelper.IsDesignMode) { #if NETFRAMEWORK try diff --git a/src/vstest.console/CommandLine/Executor.cs b/src/vstest.console/CommandLine/Executor.cs index 11f996557c..a2daebfb8c 100644 --- a/src/vstest.console/CommandLine/Executor.cs +++ b/src/vstest.console/CommandLine/Executor.cs @@ -26,6 +26,8 @@ using Abstraction::Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; using Microsoft.VisualStudio.TestPlatform.Utilities; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; using CommandLineResources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources.Resources; @@ -61,6 +63,7 @@ internal class Executor private readonly IProcessHelper _processHelper; private readonly IEnvironment _environment; private readonly IRunSettingsProvider _runSettingsProvider; + private readonly IRunSettingsHelper _runSettingsHelper; private bool _showHelp; /// @@ -91,11 +94,16 @@ internal class Executor } internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSource, IProcessHelper processHelper, IEnvironment environment) - : this(output, testPlatformEventSource, processHelper, environment, RunSettingsManager.Instance) + : this(output, testPlatformEventSource, processHelper, environment, RunSettingsManager.Instance, RunSettingsHelper.Instance) { } internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSource, IProcessHelper processHelper, IEnvironment environment, IRunSettingsProvider runSettingsProvider) + : this(output, testPlatformEventSource, processHelper, environment, runSettingsProvider, RunSettingsHelper.Instance) + { + } + + internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSource, IProcessHelper processHelper, IEnvironment environment, IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper) { DebuggerBreakpoint.AttachVisualStudioDebugger(WellKnownDebugEnvironmentVariables.VSTEST_RUNNER_DEBUG_ATTACHVS); DebuggerBreakpoint.WaitForNativeDebugger(WellKnownDebugEnvironmentVariables.VSTEST_RUNNER_NATIVE_DEBUG); @@ -107,6 +115,7 @@ internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSour _processHelper = processHelper; _environment = environment; _runSettingsProvider = runSettingsProvider; + _runSettingsHelper = runSettingsHelper; } /// @@ -228,7 +237,7 @@ private int GetArgumentProcessors(string[] args, out List pr { processors = new List(); int result = 0; - var processorFactory = ArgumentProcessorFactory.Create(runSettingsProvider: _runSettingsProvider); + var processorFactory = ArgumentProcessorFactory.Create(runSettingsProvider: _runSettingsProvider, runSettingsHelper: _runSettingsHelper); for (var index = 0; index < args.Length; index++) { var arg = args[index]; diff --git a/src/vstest.console/Processors/CLIRunSettingsArgumentProcessor.cs b/src/vstest.console/Processors/CLIRunSettingsArgumentProcessor.cs index a6ddc77bc6..a5ac978b14 100644 --- a/src/vstest.console/Processors/CLIRunSettingsArgumentProcessor.cs +++ b/src/vstest.console/Processors/CLIRunSettingsArgumentProcessor.cs @@ -11,7 +11,7 @@ using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; using Microsoft.VisualStudio.TestPlatform.Common.Utilities; using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; using CommandLineResources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources.Resources; @@ -30,10 +30,12 @@ internal class CliRunSettingsArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; + private readonly IRunSettingsHelper _runSettingsHelper; - public CliRunSettingsArgumentProcessor(IRunSettingsProvider runSettingsProvider) + public CliRunSettingsArgumentProcessor(IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper) { _runSettingsProvider = runSettingsProvider; + _runSettingsHelper = runSettingsHelper; } /// @@ -49,7 +51,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new CliRunSettingsArgumentExecutor(_runSettingsProvider, CommandLineOptions.Instance)); + new CliRunSettingsArgumentExecutor(_runSettingsProvider, CommandLineOptions.Instance, _runSettingsHelper)); set => _executor = value; } @@ -74,11 +76,13 @@ internal class CliRunSettingsArgumentExecutor : IArgumentsExecutor { private readonly IRunSettingsProvider _runSettingsManager; private readonly CommandLineOptions _commandLineOptions; + private readonly IRunSettingsHelper _runSettingsHelper; - internal CliRunSettingsArgumentExecutor(IRunSettingsProvider runSettingsManager, CommandLineOptions commandLineOptions) + internal CliRunSettingsArgumentExecutor(IRunSettingsProvider runSettingsManager, CommandLineOptions commandLineOptions, IRunSettingsHelper runSettingsHelper) { _runSettingsManager = runSettingsManager; _commandLineOptions = commandLineOptions; + _runSettingsHelper = runSettingsHelper; } public void Initialize(string? argument) @@ -239,7 +243,7 @@ private void UpdateFrameworkAndPlatform(string key, string value) bool success = Enum.TryParse(value, true, out var architecture); if (success) { - RunSettingsHelper.Instance.IsDefaultTargetArchitecture = false; + _runSettingsHelper.IsDefaultTargetArchitecture = false; _commandLineOptions.TargetArchitecture = architecture; } } diff --git a/src/vstest.console/Processors/PlatformArgumentProcessor.cs b/src/vstest.console/Processors/PlatformArgumentProcessor.cs index 621d3688b3..5a1835be13 100644 --- a/src/vstest.console/Processors/PlatformArgumentProcessor.cs +++ b/src/vstest.console/Processors/PlatformArgumentProcessor.cs @@ -9,7 +9,7 @@ using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; using Microsoft.VisualStudio.TestPlatform.Common.Utilities; using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; using CommandLineResources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources.Resources; @@ -29,10 +29,12 @@ internal class PlatformArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; + private readonly IRunSettingsHelper _runSettingsHelper; - public PlatformArgumentProcessor(IRunSettingsProvider runSettingsProvider) + public PlatformArgumentProcessor(IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper) { _runSettingsProvider = runSettingsProvider; + _runSettingsHelper = runSettingsHelper; } /// @@ -48,7 +50,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new PlatformArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider)); + new PlatformArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider, _runSettingsHelper)); set => _executor = value; } @@ -80,6 +82,8 @@ internal class PlatformArgumentExecutor : IArgumentExecutor private readonly IRunSettingsProvider _runSettingsManager; + private readonly IRunSettingsHelper _runSettingsHelper; + public const string RunSettingsPath = "RunConfiguration.TargetPlatform"; /// @@ -87,12 +91,15 @@ internal class PlatformArgumentExecutor : IArgumentExecutor /// /// The options. /// The runsettings manager. - public PlatformArgumentExecutor(CommandLineOptions options, IRunSettingsProvider runSettingsManager) + /// The runsettings helper. + public PlatformArgumentExecutor(CommandLineOptions options, IRunSettingsProvider runSettingsManager, IRunSettingsHelper runSettingsHelper) { ValidateArg.NotNull(options, nameof(options)); ValidateArg.NotNull(runSettingsManager, nameof(runSettingsManager)); + ValidateArg.NotNull(runSettingsHelper, nameof(runSettingsHelper)); _commandLineOptions = options; _runSettingsManager = runSettingsManager; + _runSettingsHelper = runSettingsHelper; } @@ -125,7 +132,7 @@ public void Initialize(string? argument) if (validPlatform) { - RunSettingsHelper.Instance.IsDefaultTargetArchitecture = false; + _runSettingsHelper.IsDefaultTargetArchitecture = false; _commandLineOptions.TargetArchitecture = platform; _runSettingsManager.UpdateRunSettingsNode(RunSettingsPath, platform.ToString()); } diff --git a/src/vstest.console/Processors/PortArgumentProcessor.cs b/src/vstest.console/Processors/PortArgumentProcessor.cs index f5fbbe818b..0e12dc46cf 100644 --- a/src/vstest.console/Processors/PortArgumentProcessor.cs +++ b/src/vstest.console/Processors/PortArgumentProcessor.cs @@ -12,7 +12,7 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Abstraction::Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; using Abstraction::Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; -using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; using CommandLineResources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources.Resources; @@ -30,6 +30,12 @@ internal class PortArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly IRunSettingsHelper _runSettingsHelper; + + public PortArgumentProcessor(IRunSettingsHelper runSettingsHelper) + { + _runSettingsHelper = runSettingsHelper; + } /// /// Gets the metadata. @@ -43,7 +49,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new PortArgumentExecutor(CommandLineOptions.Instance, TestRequestManager.Instance)); + new PortArgumentExecutor(CommandLineOptions.Instance, TestRequestManager.Instance, _runSettingsHelper)); set => _executor = value; } @@ -99,6 +105,11 @@ internal class PortArgumentExecutor : IArgumentExecutor /// private readonly IProcessHelper _processHelper; + /// + /// Used to flag that the run was started from an Editor or IDE. + /// + private readonly IRunSettingsHelper _runSettingsHelper; + /// /// Default constructor. /// @@ -106,32 +117,33 @@ internal class PortArgumentExecutor : IArgumentExecutor /// The options. /// /// Test request manager - public PortArgumentExecutor(CommandLineOptions options, ITestRequestManager testRequestManager) - : this(options, testRequestManager, InitializeDesignMode, new ProcessHelper()) + /// The runsettings helper. + public PortArgumentExecutor(CommandLineOptions options, ITestRequestManager testRequestManager, IRunSettingsHelper runSettingsHelper) + : this(options, testRequestManager, InitializeDesignMode, new ProcessHelper(), runSettingsHelper) { } /// /// For Unit testing only /// - internal PortArgumentExecutor(CommandLineOptions options, ITestRequestManager testRequestManager, IProcessHelper processHelper) - : this(options, testRequestManager, InitializeDesignMode, processHelper) + internal PortArgumentExecutor(CommandLineOptions options, ITestRequestManager testRequestManager, IProcessHelper processHelper, IRunSettingsHelper runSettingsHelper) + : this(options, testRequestManager, InitializeDesignMode, processHelper, runSettingsHelper) { } /// /// For Unit testing only /// - internal PortArgumentExecutor(CommandLineOptions options, ITestRequestManager testRequestManager, Func designModeInitializer, IProcessHelper processHelper) + internal PortArgumentExecutor(CommandLineOptions options, ITestRequestManager testRequestManager, Func designModeInitializer, IProcessHelper processHelper, IRunSettingsHelper runSettingsHelper) { ValidateArg.NotNull(options, nameof(options)); _commandLineOptions = options; _testRequestManager = testRequestManager; _designModeInitializer = designModeInitializer; _processHelper = processHelper; + _runSettingsHelper = runSettingsHelper; } - #region IArgumentExecutor /// @@ -148,7 +160,7 @@ public void Initialize(string? argument) _port = portNumber; _commandLineOptions.Port = portNumber; _commandLineOptions.IsDesignMode = true; - RunSettingsHelper.Instance.IsDesignMode = true; + _runSettingsHelper.IsDesignMode = true; _designModeClient = _designModeInitializer?.Invoke(_commandLineOptions.ParentProcessId, _processHelper); } diff --git a/src/vstest.console/Processors/RunSettingsArgumentProcessor.cs b/src/vstest.console/Processors/RunSettingsArgumentProcessor.cs index 84517a465a..053c8aa147 100644 --- a/src/vstest.console/Processors/RunSettingsArgumentProcessor.cs +++ b/src/vstest.console/Processors/RunSettingsArgumentProcessor.cs @@ -31,10 +31,12 @@ internal class RunSettingsArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; + private readonly IRunSettingsHelper _runSettingsHelper; - public RunSettingsArgumentProcessor(IRunSettingsProvider runSettingsProvider) + public RunSettingsArgumentProcessor(IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper) { _runSettingsProvider = runSettingsProvider; + _runSettingsHelper = runSettingsHelper; } /// @@ -50,7 +52,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new RunSettingsArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider)); + new RunSettingsArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider, _runSettingsHelper)); set => _executor = value; } @@ -75,13 +77,15 @@ internal class RunSettingsArgumentExecutor : IArgumentExecutor { private readonly CommandLineOptions _commandLineOptions; private readonly IRunSettingsProvider _runSettingsManager; + private readonly IRunSettingsHelper _runSettingsHelper; internal IFileHelper FileHelper { get; set; } - internal RunSettingsArgumentExecutor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsManager) + internal RunSettingsArgumentExecutor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsManager, IRunSettingsHelper runSettingsHelper) { _commandLineOptions = commandLineOptions; _runSettingsManager = runSettingsManager; + _runSettingsHelper = runSettingsHelper; FileHelper = new FileHelper(); } @@ -150,7 +154,7 @@ private void ExtractFrameworkAndPlatform() var platformStr = _runSettingsManager.QueryRunSettingsNode(PlatformArgumentExecutor.RunSettingsPath); if (Enum.TryParse(platformStr, true, out var architecture)) { - RunSettingsHelper.Instance.IsDefaultTargetArchitecture = false; + _runSettingsHelper.IsDefaultTargetArchitecture = false; _commandLineOptions.TargetArchitecture = architecture; } } diff --git a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs index d05037f287..f0ad9115a6 100644 --- a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs +++ b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs @@ -11,6 +11,8 @@ using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.Utilities; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; namespace Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; @@ -51,11 +53,17 @@ protected ArgumentProcessorFactory(IEnumerable argumentProce /// Defaults to the ambient when not provided, so that /// callers (and the composition root) can inject an isolated instance instead of sharing static state. /// + /// + /// The run settings helper that the created argument processors write request-scoped flags to. + /// Defaults to the ambient when not provided, so that + /// callers (and the composition root) can inject an isolated instance instead of sharing static state. + /// /// ArgumentProcessorFactory. - internal static ArgumentProcessorFactory Create(IFeatureFlag? featureFlag = null, IRunSettingsProvider? runSettingsProvider = null) + internal static ArgumentProcessorFactory Create(IFeatureFlag? featureFlag = null, IRunSettingsProvider? runSettingsProvider = null, IRunSettingsHelper? runSettingsHelper = null) { runSettingsProvider ??= RunSettingsManager.Instance; - var defaultArgumentProcessor = GetDefaultArgumentProcessors(runSettingsProvider); + runSettingsHelper ??= RunSettingsHelper.Instance; + var defaultArgumentProcessor = GetDefaultArgumentProcessors(runSettingsProvider, runSettingsHelper); if (!(featureFlag ?? FeatureFlag.Instance).IsSet(FeatureFlag.VSTEST_DISABLE_ARTIFACTS_POSTPROCESSING)) { @@ -189,7 +197,7 @@ public IEnumerable GetArgumentProcessorsToAlwaysExecute() .Where(lazyProcessor => lazyProcessor.Metadata.Value.IsSpecialCommand && lazyProcessor.Metadata.Value.AlwaysExecute); } - private static IList GetDefaultArgumentProcessors(IRunSettingsProvider runSettingsProvider) => new List { + private static IList GetDefaultArgumentProcessors(IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper) => new List { new HelpArgumentProcessor(), new TestSourceArgumentProcessor(), new ListTestsArgumentProcessor(runSettingsProvider), @@ -199,14 +207,14 @@ public IEnumerable GetArgumentProcessorsToAlwaysExecute() new TestAdapterLoadingStrategyArgumentProcessor(runSettingsProvider), new TestCaseFilterArgumentProcessor(), new ParentProcessIdArgumentProcessor(), - new PortArgumentProcessor(), - new RunSettingsArgumentProcessor(runSettingsProvider), - new PlatformArgumentProcessor(runSettingsProvider), + new PortArgumentProcessor(runSettingsHelper), + new RunSettingsArgumentProcessor(runSettingsProvider, runSettingsHelper), + new PlatformArgumentProcessor(runSettingsProvider, runSettingsHelper), new FrameworkArgumentProcessor(runSettingsProvider), new EnableLoggerArgumentProcessor(runSettingsProvider), new ParallelArgumentProcessor(runSettingsProvider), new EnableDiagArgumentProcessor(), - new CliRunSettingsArgumentProcessor(runSettingsProvider), + new CliRunSettingsArgumentProcessor(runSettingsProvider, runSettingsHelper), new ResultsDirectoryArgumentProcessor(runSettingsProvider), new InIsolationArgumentProcessor(runSettingsProvider), new CollectArgumentProcessor(runSettingsProvider), diff --git a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs index cbc665aec0..c74264a588 100644 --- a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs +++ b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs @@ -66,6 +66,7 @@ internal class TestRequestManager : ITestRequestManager private readonly ITestRunAttachmentsProcessingManager _attachmentsProcessingManager; private readonly IEnvironment _environment; private readonly IEnvironmentVariableHelper _environmentVariableHelper; + private readonly IRunSettingsHelper _runSettingsHelper; /// /// Maintains the current active execution request. @@ -108,7 +109,8 @@ public TestRequestManager() new ProcessHelper(), new TestRunAttachmentsProcessingManager(TestPlatformEventSource.Instance, new DataCollectorAttachmentsProcessorsFactory()), new PlatformEnvironment(), - new EnvironmentVariableHelper()) + new EnvironmentVariableHelper(), + RunSettingsHelper.Instance) { } @@ -123,6 +125,33 @@ internal TestRequestManager( ITestRunAttachmentsProcessingManager attachmentsProcessingManager, IEnvironment environment, IEnvironmentVariableHelper environmentVariableHelper) + : this( + commandLineOptions, + testPlatform, + testRunResultAggregator, + testPlatformEventSource, + inferHelper, + metricsPublisher, + processHelper, + attachmentsProcessingManager, + environment, + environmentVariableHelper, + RunSettingsHelper.Instance) + { + } + + internal TestRequestManager( + CommandLineOptions commandLineOptions, + ITestPlatform testPlatform, + TestRunResultAggregator testRunResultAggregator, + ITestPlatformEventSource testPlatformEventSource, + InferHelper inferHelper, + Task metricsPublisher, + IProcessHelper processHelper, + ITestRunAttachmentsProcessingManager attachmentsProcessingManager, + IEnvironment environment, + IEnvironmentVariableHelper environmentVariableHelper, + IRunSettingsHelper runSettingsHelper) { _testPlatform = testPlatform; _commandLineOptions = commandLineOptions; @@ -134,6 +163,7 @@ internal TestRequestManager( _attachmentsProcessingManager = attachmentsProcessingManager; _environment = environment; _environmentVariableHelper = environmentVariableHelper; + _runSettingsHelper = runSettingsHelper; } /// @@ -779,7 +809,7 @@ private bool UpdateRunSettingsIfRequired( // Other scenarios, most notably .NET Framework with MultiTFM disabled, will use the old default X86 architecture. } - EqtTrace.Verbose($"TestRequestManager.UpdateRunSettingsIfRequired: Default architecture: {defaultArchitecture} IsDefaultTargetArchitecture: {RunSettingsHelper.Instance.IsDefaultTargetArchitecture}, Current process architecture: {_processHelper.GetCurrentProcessArchitecture()} OperatingSystem: {_environment.OperatingSystem}."); + EqtTrace.Verbose($"TestRequestManager.UpdateRunSettingsIfRequired: Default architecture: {defaultArchitecture} IsDefaultTargetArchitecture: {_runSettingsHelper.IsDefaultTargetArchitecture}, Current process architecture: {_processHelper.GetCurrentProcessArchitecture()} OperatingSystem: {_environment.OperatingSystem}."); // True when runsettings don't set platforml. False when runsettings force platform // in both cases the sourceToArchitectureMap is populated with the real architecture as we inferred it @@ -856,7 +886,7 @@ private bool UpdateRunSettingsIfRequired( Architecture GetDefaultArchitecture(RunConfiguration runConfiguration) { - if (!RunSettingsHelper.Instance.IsDefaultTargetArchitecture) + if (!_runSettingsHelper.IsDefaultTargetArchitecture) { return runConfiguration.TargetPlatform; } diff --git a/test/vstest.console.UnitTests/Processors/CLIRunSettingsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/CLIRunSettingsArgumentProcessorTests.cs index 08dc751cd2..91a499962b 100644 --- a/test/vstest.console.UnitTests/Processors/CLIRunSettingsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/CLIRunSettingsArgumentProcessorTests.cs @@ -9,6 +9,8 @@ using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; using Microsoft.VisualStudio.TestPlatform.Common; using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; using Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -22,6 +24,7 @@ public class CliRunSettingsArgumentProcessorTests private readonly TestableRunSettingsProvider _settingsProvider; private readonly CliRunSettingsArgumentExecutor _executor; private readonly CommandLineOptions _commandLineOptions; + private readonly IRunSettingsHelper _runSettingsHelper; private readonly string _defaultRunSettings = string.Join(Environment.NewLine, "", "", @@ -56,7 +59,8 @@ public CliRunSettingsArgumentProcessorTests() { _commandLineOptions = CommandLineOptions.Instance; _settingsProvider = new TestableRunSettingsProvider(); - _executor = new CliRunSettingsArgumentExecutor(_settingsProvider, _commandLineOptions); + _runSettingsHelper = new RunSettingsHelper(); + _executor = new CliRunSettingsArgumentExecutor(_settingsProvider, _commandLineOptions, _runSettingsHelper); } [TestCleanup] @@ -68,14 +72,14 @@ public void Cleanup() [TestMethod] public void GetMetadataShouldReturnRunSettingsArgumentProcessorCapabilities() { - var processor = new CliRunSettingsArgumentProcessor(new TestableRunSettingsProvider()); + var processor = new CliRunSettingsArgumentProcessor(new TestableRunSettingsProvider(), _runSettingsHelper); Assert.IsTrue(processor.Metadata.Value is CliRunSettingsArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnRunSettingsArgumentProcessorCapabilities() { - var processor = new CliRunSettingsArgumentProcessor(new TestableRunSettingsProvider()); + var processor = new CliRunSettingsArgumentProcessor(new TestableRunSettingsProvider(), _runSettingsHelper); Assert.IsTrue(processor.Executor!.Value is CliRunSettingsArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs index ae9ccacd08..22091e8bd2 100644 --- a/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs @@ -5,6 +5,8 @@ using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; using Microsoft.VisualStudio.TestPlatform.Common.Utilities; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; using Microsoft.VisualStudio.TestTools.UnitTesting; using vstest.console.UnitTests.Processors; @@ -16,11 +18,13 @@ public class PlatformArgumentProcessorTests { private readonly PlatformArgumentExecutor _executor; private readonly TestableRunSettingsProvider _runSettingsProvider; + private readonly IRunSettingsHelper _runSettingsHelper; public PlatformArgumentProcessorTests() { _runSettingsProvider = new TestableRunSettingsProvider(); - _executor = new PlatformArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider); + _runSettingsHelper = new RunSettingsHelper(); + _executor = new PlatformArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider, _runSettingsHelper); } [TestCleanup] @@ -32,14 +36,14 @@ public void TestCleanup() [TestMethod] public void GetMetadataShouldReturnPlatformArgumentProcessorCapabilities() { - var processor = new PlatformArgumentProcessor(new TestableRunSettingsProvider()); + var processor = new PlatformArgumentProcessor(new TestableRunSettingsProvider(), _runSettingsHelper); Assert.IsTrue(processor.Metadata.Value is PlatformArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnPlatformArgumentExecutor() { - var processor = new PlatformArgumentProcessor(new TestableRunSettingsProvider()); + var processor = new PlatformArgumentProcessor(new TestableRunSettingsProvider(), _runSettingsHelper); Assert.IsTrue(processor.Executor!.Value is PlatformArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs index 09e78728de..45b694314e 100644 --- a/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs @@ -11,6 +11,8 @@ using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -23,6 +25,7 @@ public class PortArgumentProcessorTests private readonly Mock _mockProcessHelper; private readonly Mock _testDesignModeClient; private readonly Mock _testRequestManager; + private readonly IRunSettingsHelper _runSettingsHelper; private PortArgumentExecutor _executor; public PortArgumentProcessorTests() @@ -30,20 +33,21 @@ public PortArgumentProcessorTests() _mockProcessHelper = new Mock(); _testDesignModeClient = new Mock(); _testRequestManager = new Mock(); - _executor = new PortArgumentExecutor(CommandLineOptions.Instance, _testRequestManager.Object); + _runSettingsHelper = new RunSettingsHelper(); + _executor = new PortArgumentExecutor(CommandLineOptions.Instance, _testRequestManager.Object, _runSettingsHelper); } [TestMethod] public void GetMetadataShouldReturnPortArgumentProcessorCapabilities() { - var processor = new PortArgumentProcessor(); + var processor = new PortArgumentProcessor(_runSettingsHelper); Assert.IsTrue(processor.Metadata.Value is PortArgumentProcessorCapabilities); } [TestMethod] public void GetExecutorShouldReturnPortArgumentProcessorCapabilities() { - var processor = new PortArgumentProcessor(); + var processor = new PortArgumentProcessor(_runSettingsHelper); Assert.IsTrue(processor.Executor!.Value is PortArgumentExecutor); } @@ -102,12 +106,13 @@ public void ExecutorInitializeShouldSetDesignMode() _executor.Initialize(port.ToString(CultureInfo.InvariantCulture)); Assert.IsTrue(CommandLineOptions.Instance.IsDesignMode); + Assert.IsTrue(_runSettingsHelper.IsDesignMode); } [TestMethod] public void ExecutorInitializeShouldSetProcessExitCallback() { - _executor = new PortArgumentExecutor(CommandLineOptions.Instance, _testRequestManager.Object, _mockProcessHelper.Object); + _executor = new PortArgumentExecutor(CommandLineOptions.Instance, _testRequestManager.Object, _mockProcessHelper.Object, _runSettingsHelper); int port = 2345; #if NET5_0_OR_GREATER var pid = Environment.ProcessId; @@ -127,7 +132,7 @@ public void ExecutorInitializeShouldSetProcessExitCallback() public void ExecutorExecuteForValidConnectionReturnsArgumentProcessorResultSuccess() { _executor = new PortArgumentExecutor(CommandLineOptions.Instance, _testRequestManager.Object, - (parentProcessId, ph) => _testDesignModeClient.Object, _mockProcessHelper.Object); + (parentProcessId, ph) => _testDesignModeClient.Object, _mockProcessHelper.Object, _runSettingsHelper); int port = 2345; _executor.Initialize(port.ToString(CultureInfo.InvariantCulture)); @@ -143,7 +148,7 @@ public void ExecutorExecuteForValidConnectionReturnsArgumentProcessorResultSucce public void ExecutorExecuteForFailedConnectionShouldThrowCommandLineException() { _executor = new PortArgumentExecutor(CommandLineOptions.Instance, _testRequestManager.Object, - (parentProcessId, ph) => _testDesignModeClient.Object, _mockProcessHelper.Object); + (parentProcessId, ph) => _testDesignModeClient.Object, _mockProcessHelper.Object, _runSettingsHelper); _testDesignModeClient.Setup(td => td.ConnectToClientAndProcessRequests(It.IsAny(), It.IsAny())).Callback(() => throw new TimeoutException()); @@ -171,7 +176,8 @@ public void ExecutorExecuteSetsParentProcessIdOnDesignModeInitializer() actualParentProcessId = ppid; return _testDesignModeClient.Object; }, - _mockProcessHelper.Object + _mockProcessHelper.Object, + _runSettingsHelper ); int port = 2345; diff --git a/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessorTests.cs index 535b6877e4..3067f333a4 100644 --- a/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessorTests.cs @@ -12,6 +12,7 @@ using Microsoft.VisualStudio.TestPlatform.Common.Utilities; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -40,14 +41,14 @@ public void TestCleanup() [TestMethod] public void GetMetadataShouldReturnRunSettingsArgumentProcessorCapabilities() { - var processor = new RunSettingsArgumentProcessor(new TestableRunSettingsProvider()); + var processor = new RunSettingsArgumentProcessor(new TestableRunSettingsProvider(), new RunSettingsHelper()); Assert.IsTrue(processor.Metadata.Value is RunSettingsArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnRunSettingsArgumentExecutor() { - var processor = new RunSettingsArgumentProcessor(new TestableRunSettingsProvider()); + var processor = new RunSettingsArgumentProcessor(new TestableRunSettingsProvider(), new RunSettingsHelper()); Assert.IsTrue(processor.Executor!.Value is RunSettingsArgumentExecutor); } @@ -77,14 +78,14 @@ public void CapabilitiesShouldReturnAppropriateProperties() [TestMethod] public void InitializeShouldThrowExceptionIfArgumentIsNull() { - var ex = Assert.ThrowsExactly(() => new RunSettingsArgumentExecutor(CommandLineOptions.Instance, null!).Initialize(null)); + var ex = Assert.ThrowsExactly(() => new RunSettingsArgumentExecutor(CommandLineOptions.Instance, null!, new RunSettingsHelper()).Initialize(null)); Assert.Contains("The /Settings parameter requires a settings file to be provided.", ex.Message); } [TestMethod] public void InitializeShouldThrowExceptionIfArgumentIsWhiteSpace() { - var ex = Assert.ThrowsExactly(() => new RunSettingsArgumentExecutor(CommandLineOptions.Instance, null!).Initialize(" ")); + var ex = Assert.ThrowsExactly(() => new RunSettingsArgumentExecutor(CommandLineOptions.Instance, null!, new RunSettingsHelper()).Initialize(" ")); Assert.Contains("The /Settings parameter requires a settings file to be provided.", ex.Message); } @@ -93,7 +94,7 @@ public void InitializeShouldThrowExceptionIfFileDoesNotExist() { var fileName = "C:\\Imaginary\\nonExistentFile.txt"; - var executor = new RunSettingsArgumentExecutor(CommandLineOptions.Instance, null!); + var executor = new RunSettingsArgumentExecutor(CommandLineOptions.Instance, null!, new RunSettingsHelper()); var mockFileHelper = new Mock(); mockFileHelper.Setup(fh => fh.Exists(It.IsAny())).Returns(false); @@ -407,7 +408,7 @@ internal TestableRunSettingsArgumentExecutor( CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsManager, string? runSettings) - : base(commandLineOptions, runSettingsManager) + : base(commandLineOptions, runSettingsManager, new RunSettingsHelper()) { _runSettingsString = runSettings; diff --git a/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs b/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs index 3ce52e3728..6ae5264754 100644 --- a/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs +++ b/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs @@ -8,6 +8,8 @@ using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; using Microsoft.VisualStudio.TestPlatform.Utilities; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -167,11 +169,18 @@ private static IEnumerable GetArgumentProcessors(bool specia foreach (var processor in allProcessors) { - // Some processors require an IRunSettingsProvider via constructor injection; the rest are parameterless. - var runSettingsCtor = processor.GetConstructor([typeof(IRunSettingsProvider)]); - var instance = (runSettingsCtor is not null - ? runSettingsCtor.Invoke([new TestableRunSettingsProvider()]) - : Activator.CreateInstance(processor)) as IArgumentProcessor; + // Processors declare different constructor shapes: some take an IRunSettingsProvider, some take + // an IRunSettingsHelper, some take both, and the rest are parameterless. Pick the matching one. + var runSettingsProvider = new TestableRunSettingsProvider(); + var runSettingsHelper = new RunSettingsHelper(); + + var instance = (processor.GetConstructor([typeof(IRunSettingsProvider), typeof(IRunSettingsHelper)]) is { } providerAndHelperCtor + ? providerAndHelperCtor.Invoke([runSettingsProvider, runSettingsHelper]) + : processor.GetConstructor([typeof(IRunSettingsProvider)]) is { } providerCtor + ? providerCtor.Invoke([runSettingsProvider]) + : processor.GetConstructor([typeof(IRunSettingsHelper)]) is { } helperCtor + ? helperCtor.Invoke([runSettingsHelper]) + : Activator.CreateInstance(processor)) as IArgumentProcessor; Assert.IsNotNull(instance, $"Unable to instantiate processor: {processor}"); var specialProcessor = instance.Metadata.Value.IsSpecialCommand; diff --git a/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs b/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs index b6484c8ebe..5a86330724 100644 --- a/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs +++ b/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs @@ -63,6 +63,7 @@ public class TestRequestManagerTests private readonly Mock _mockAttachmentsProcessingManager; private readonly Mock _mockEnvironment; private readonly Mock _mockEnvironmentVariableHelper; + private readonly IRunSettingsHelper _runSettingsHelper; private const string DefaultRunsettings = @" @@ -85,6 +86,7 @@ public TestRequestManagerTests() _mockProcessHelper = new Mock(); _mockEnvironment = new Mock(); _mockEnvironmentVariableHelper = new Mock(); + _runSettingsHelper = new RunSettingsHelper(); _mockMetricsPublisher = new Mock(); _mockMetricsPublisherTask = Task.FromResult(_mockMetricsPublisher.Object); @@ -99,7 +101,8 @@ public TestRequestManagerTests() _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, - _mockEnvironmentVariableHelper.Object); + _mockEnvironmentVariableHelper.Object, + _runSettingsHelper); _mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) .Returns(_mockDiscoveryRequest.Object); _mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) @@ -2842,14 +2845,13 @@ private void DiscoverTestsIfThrowsExceptionShouldThrowOut(Exception exception) [DataRow("x86")] [DataRow("x64")] [DataRow("arm64")] - // Don't parallelize because we can run into conflict with GetDefaultArchitecture -> RunSettingsHelper.Instance.IsDefaultTargetArchitecture - // which is set by some other test. - [DoNotParallelize] public void SettingDefaultPlatformUsesItForAnyCPUSourceButNotForNonAnyCPUSource(string defaultPlatform) { // -- Arrange - RunSettingsHelper.Instance.IsDefaultTargetArchitecture = true; + // GetDefaultArchitecture reads IsDefaultTargetArchitecture from the injected IRunSettingsHelper, so we set it + // on that per-test instance rather than the shared RunSettingsHelper.Instance static. That keeps the test isolated. + _runSettingsHelper.IsDefaultTargetArchitecture = true; var payload = new DiscoveryRequestPayload() { Sources = new List() { "AnyCPU.dll", "x64.dll" }, From 1a14384a9203d8aa6a980e72204a153b89ca8446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Fri, 3 Jul 2026 18:38:53 +0200 Subject: [PATCH 16/87] Add a test that a writer and a reader share the injected IRunSettingsHelper (#16207) The whole point of injecting IRunSettingsHelper is that a flag written while parsing arguments is read back later through the same instance. Nothing in the suite pinned that down, so a later change could hand the writer and the reader two different objects and design-mode propagation would break with no test turning red. This adds one test that drives both ends against a single injected instance: PlatformArgumentExecutor is the writer (it sets IsDefaultTargetArchitecture to false), and TestRequestManager is the reader (GetDefaultArchitecture consults the flag). The run settings ask for ARM, an architecture the tests never run on, so the two branches of the flag resolve to different results and the assertion can only pass when the reader observes the writer's flip through the shared helper. The writer gets a throwaway CommandLineOptions to keep the write isolated to the helper under test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../TestRequestManagerTests.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs b/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs index 5a86330724..9856dc414b 100644 --- a/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs +++ b/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs @@ -36,6 +36,7 @@ using Moq; +using vstest.console.UnitTests.Processors; using vstest.console.UnitTests.TestDoubles; using Constants = Microsoft.VisualStudio.TestPlatform.ObjectModel.Constants; @@ -2891,6 +2892,57 @@ public void SettingDefaultPlatformUsesItForAnyCPUSourceButNotForNonAnyCPUSource( actualSourceToSourceDetailMap!["x64.dll"].Architecture.Should().Be(Architecture.X64); } + [TestMethod] + public void WritingIsDefaultTargetArchitectureThroughPlatformArgumentExecutorIsObservedByTestRequestManager() + { + // -- Arrange + // The --Platform argument executor (the writer) and this TestRequestManager (the reader) are handed the same + // IRunSettingsHelper instance: _runSettingsHelper, which was injected into the manager in the test constructor. + // This guards the same-instance contract of the injection - a flag the writer sets has to be observed by the + // reader precisely because both ends resolve to one object and not to two separate copies. + _runSettingsHelper.IsDefaultTargetArchitecture.Should().BeTrue("the flag defaults to true before any --Platform is parsed"); + + // GetDefaultArchitecture honors only while IsDefaultTargetArchitecture is true; once the flag + // is false it returns the run configuration's TargetPlatform default instead. ARM is used as the + // because it is never the architecture the tests actually run on, so the two branches resolve to different values + // and the assertion below can only pass if the writer's flip was observed by the reader through the shared helper. + var payload = new DiscoveryRequestPayload() + { + Sources = new List() { "AnyCPU.dll" }, + RunSettings = + @" + + + ARM + + " + }; + _mockAssemblyMetadataProvider.Setup(m => m.GetArchitecture("AnyCPU.dll")).Returns(Architecture.AnyCPU); + + Dictionary? actualSourceToSourceDetailMap = null; + var mockDiscoveryRequest = new Mock(); + _mockTestPlatform.Setup(mt => mt.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) + .Callback((IRequestData _, DiscoveryCriteria _, TestPlatformOptions _, Dictionary sourceToSourceDetailMap, IWarningLogger _) => + actualSourceToSourceDetailMap = sourceToSourceDetailMap) + .Returns(mockDiscoveryRequest.Object); + + // -- Act + // Writer: parsing "--Platform x64" flips IsDefaultTargetArchitecture to false on the shared helper. A throwaway + // CommandLineOptions keeps the write isolated to the helper under test. + new PlatformArgumentExecutor(new CommandLineOptions(), new TestableRunSettingsProvider(), _runSettingsHelper) + .Initialize("x64"); + _runSettingsHelper.IsDefaultTargetArchitecture.Should().BeFalse("the --Platform executor writes the flag on the injected instance"); + + // Reader: the manager infers the AnyCPU source's architecture through the same helper instance. + _testRequestManager.DiscoverTests(payload, new Mock().Object, _protocolConfig); + + // -- Assert + actualSourceToSourceDetailMap.Should().NotBeNull(); + actualSourceToSourceDetailMap!["AnyCPU.dll"].Architecture.Should().Be( + Constants.DefaultPlatform, + "with IsDefaultTargetArchitecture flipped to false through the shared helper the manager returns the run configuration's TargetPlatform default and ignores ARM"); + } + [TestMethod] public void UsingInvalidValueForDefaultPlatformSettingThrowsSettingsException() { From 8f77fae066b008eed19205bc919d2c9ff550772a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Fri, 3 Jul 2026 18:42:43 +0200 Subject: [PATCH 17/87] Run Microsoft.Testing.Platform test apps under vstest.console and datacollector (#16201) * MTP: detect Microsoft.Testing.Platform apps and carry ExecutionPreference Add ExecutionPreference {Default, MicrosoftTestingPlatform} to ObjectModel and SourceDetail. Detect MTP apps in AssemblyMetadataProvider by reading the assembly-level AssemblyMetadata("Microsoft.Testing.Platform.Application", "true") attribute, expose it through InferHelper.DetectExecutionPreference, and thread a per-source ExecutionPreference map through TestRequestManager into SourceDetail. TestEngine now groups unique run configurations by ExecutionPreference as well as framework/architecture, and forces isolation for MTP sources so they never run in-process. This is the detection/plumbing groundwork for routing MTP sources to an MTP-protocol proxy. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * MTP: add JSON-RPC client, node converter, and proxies; route MTP sources Implements the Microsoft.Testing.Platform (MTP) execution path in CrossPlatEngine: - MtpServerConnection: launches an MTP app in `--server` mode, opens a loopback TCP listener that the app dials back into, and speaks JSON-RPC 2.0 with Content-Length framing. Raises testUpdates/log events. - MtpTestNodeConverter: converts pure MTP test nodes (uid, display-name, execution-state, time.duration-ms, error.*, location.*, traits) into vstest TestCase/TestResult. vstest.* bridge props are used only as optional enrichment so an app with no vstest dependency still converts. - MtpProxyDiscoveryManager / MtpProxyExecutionManager: IProxyDiscoveryManager / IProxyExecutionManager implementations that drive an MTP app per source, translate node updates into discovery/run events, and collect attachments. - TestEngine: routes discovery and execution to the MTP proxies when a source's ExecutionPreference is MicrosoftTestingPlatform. The MTP code is guarded with `#if NETCOREAPP` and uses System.Text.Json, matching the CommunicationUtilities pattern. CrossPlatEngine now also targets net8.0 so the runner loads an MTP-enabled flavor on modern .NET; net462 falls back to the normal path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add pure-MTP MSTest test asset for MTP-under-vstest e2e net8.0 Exe with EnableMSTestRunner=true so it builds as a Microsoft.Testing.Platform app (carries the Microsoft.Testing.Platform.Application metadata attribute that vstest's MTP detection keys on). Four tests: two pass, one fails, one skipped - used to validate outcome mapping through the new MTP proxies. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Register sentinel runtime provider for MTP sources so they aren't rejected GetTestRuntimeProvidersForUniqueConfigurations looked up a vstest ITestRuntimeProvider (testhost) for every source. MTP sources have none - they're driven directly over the MTP protocol - so the group got a null-Type TestRuntimeProviderInfo, which tripped the 'No suitable test runtime provider' guard and made the parallel managers treat the workload as non-runnable (HasProvider checks Type != null). For MTP source groups, register a sentinel TestRuntimeProviderInfo(typeof(ITestRuntimeProvider)) under NETCOREAPP so the guard passes and the workload is runnable; the discovery/execution manager creators then route to MtpProxyDiscoveryManager / MtpProxyExecutionManager based on ExecutionPreference. First full end-to-end: vstest.console (net8.0) runs a pure-MTP MSTest app over the MTP protocol - discovery lists all tests, execution reports Passed 2 / Failed 1 / Skipped 1 with error message and stack trace mapped through to the console reporter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Wire datacollector (code coverage) into the MTP execution path MtpProxyExecutionManager can now own an IProxyDataCollectionManager. Before the run it calls BeforeTestRunStart to spin up datacollector.exe and merges the profiler environment variables it returns into the env vars injected into the MTP application launch. After each MTP app connects, TestHostLaunched is called with the app's process id so the collector can track it. When the run completes, AfterTestRunEnd is called and its attachments (e.g. the .coverage file) and invoked data collectors are merged into TestRunCompleteEventArgs. The MTP proxy drives HandleTestRunComplete directly rather than via raw ExecutionComplete messages, so the data-collection lifecycle is handled inline instead of reusing DataCollectionTestRunEventsHandler (which hooks the raw-message path). TestEngine routes MTP sources to the data-collection-enabled MtpProxyExecutionManager when data collection is enabled in runsettings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Decline MTP sources in the vstest testhost providers Plumb ExecutionPreference through RunConfiguration (parse/emit) and into the per-source runsettings so DefaultTestHostManager and DotnetTestHostManager return false for Microsoft.Testing.Platform sources in CanExecuteCurrentRunConfiguration. A vstest testhost can no longer claim an MTP app; routing falls through to the MTP proxies instead. Also regenerate expected-dll-frameworks.json from a clean Release pack: the netcore console now ships the net8.0 CrossPlatEngine build (the one that carries the MTP path) rather than the netstandard2.0 one. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add pure-MTP test asset with zero vstest dependency (Phase B proof) MtpPureProject references only Microsoft.Testing.Platform. It hand-rolls its own ITestFramework and reports test nodes over the MTP protocol directly, with no MSTest, no VSTestBridge, and no Microsoft.TestPlatform.ObjectModel anywhere in its closure. Deployed output carries just Microsoft.Testing.Platform.dll + the app itself (the MSTest MTP asset drags in 4 vstest DLLs: ObjectModel, VSTestBridge, CoreUtilities, PlatformAbstractions). Proven end-to-end under vstest.console via the MTP provider: - detected as MTP through the Microsoft.Testing.Platform.Application assembly metadata - Failed 1 / Passed 2 / Skipped 1 / Total 4 (matches the native MTP run) - canonical --collect:"Code Coverage" produced a real .coverage: Calculator.Add 2/2 blocks, Multiply 5/5, Divide 0/5 (uncovered by design) This is the ultimate demonstration that a framework with no vstest lineage runs under vstest.console purely over MTP, including code coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Make runtime-provider selection source-aware (two-pass) GetTestHostManagerByRunConfiguration already received the sources list but threw it away (the param was named `_`). Use it: providers that implement the new internal ISourceAwareTestRuntimeProvider get first refusal based on the actual sources, before the source-blind providers that only match by target framework. This lets a more specific provider (e.g. the upcoming MTP provider) claim a source by its shape and win without a global ordering scheme, and without relying on the other providers to decline. Providers that don't implement the interface keep their existing source-blind behaviour, and passing null sources skips the first pass entirely, so existing runs are unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Detect MTP from the source in TestEngine, not ExecutionPreference TestEngine already has the source (SourceDetail.Source) at every point where it decides how to route a run, so let it derive whether a source is a Microsoft.Testing.Platform app from the assembly itself instead of reading a pre-baked ExecutionPreference that had to be computed upstream and threaded through SourceDetail/RunConfiguration. The MTP detection (the [assembly: AssemblyMetadata(...)] PEReader probe) moves into a shared MicrosoftTestingPlatformDetector in CoreUtilities, which already uses PEReader and is befriended by CrossPlatEngine, vstest.console and the TestHostRuntimeProvider. vstest.console's AssemblyMetadataProvider now delegates to it instead of carrying its own copy of the (subtle) attribute-blob parsing. TestEngine memoizes the result per source path (concurrent, since the parallel manager creators run on multiple threads). ExecutionPreference is still set upstream for now; TestEngine simply no longer depends on it. Grouping, isolation, the sentinel provider and the MTP proxy routing all key on the source instead. Verified: pure-MTP asset still runs Failed1/Passed2/Skipped1/Total4 under vstest.console; CrossPlatEngine.UnitTests (665) and Common.UnitTests (396) green. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove ExecutionPreference; MTP is detected from the source TestEngine and the runtime-provider selection now detect Microsoft.Testing.Platform apps straight from the source (the earlier two commits), so the ExecutionPreference enum and all the plumbing that carried it through runsettings is dead weight. Delete the enum, its SourceDetail/RunConfiguration properties (parse + emit), the InferRunSettingsHelper node, InferHelper.DetectExecutionPreference and the sourceToExecutionPreferenceMap threaded through TestRequestManager, plus the inert "decline MTP" branches in the Default/Dotnet host managers. The ExecutionPreference public API entries were unshipped, so this is a clean delete. Build green, pure-MTP E2E still Failed1/Passed2/Skipped1/Total4, and ObjectModel, Utilities, vstest.console, CrossPlatEngine and Common unit suites pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Skip source-aware providers in the source-blind host-resolution pass A source-aware provider that declined by source in the first pass was still re-evaluated in the second, source-blind pass -- redundant work, and it could be wrongly re-admitted by matching only the target framework. Exclude ISourceAwareTestRuntimeProvider from the second pass so the manager owns the first-refusal contract instead of relying on each provider to return false when asked the source-blind question. The test double now returns true from its source-blind method to prove the exclusion is enforced by the manager. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Move MTP JSON-RPC wire from System.Text.Json to Jsonite (all TFMs) Rewrite the MTP client (MtpServerConnection, MtpProxy* managers, MtpTestNodeConverter, MtpClientHelpers) to serialize/parse with Jsonite instead of System.Text.Json, and remove the #if NETCOREAPP guards so the client compiles on net462, netstandard2.0 and net8.0. Jsonite is already compiled into CommunicationUtilities on every TFM and made visible to CrossPlatEngine via InternalsVisibleTo, so no extra dependency is needed and there is no System.Text.Json binding-redirect fallout on the .NET Framework runner. Add MtpJson, a small set of Jsonite DOM accessors, to centralize number coercion (int/long/double/decimal) and object/array casting. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Make MTP routing a first-class runtime provider instead of TestEngine ifs The MTP proof routed Microsoft.Testing.Platform sources by branching inside TestEngine on #if NETCOREAPP + IsMicrosoftTestingPlatformSource. That leaks protocol awareness into the engine and doesn't compile the MTP path on netfx. Replace it with a registered runtime provider and a small proxy-factory seam: - MtpTestRuntimeProvider (in the TestHostRuntimeProvider assembly) claims MTP sources via ISourceAwareTestRuntimeProvider and produces its own discovery/ execution proxy managers via a new IProxyManagerFactory. - TestEngine asks the resolved host manager `is IProxyManagerFactory` and uses it, so the engine no longer knows anything about MTP. The #if NETCOREAPP and IsMicrosoftTestingPlatformSource branches are gone. - MtpProxyManagerFactory (public, CrossPlatEngine) creates the internal MTP proxies so the out-of-assembly provider can build them without exposing the proxy classes themselves. - Group unique run configs by the source-aware provider type so MTP and classic sources split into separate hosts in a mixed run. Builds clean Debug + Release across all TFMs (net462/netstandard2.0/net8.0). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add MTP-under-vstest acceptance tests Add three acceptance tests proving MTP apps run under vstest.console end-to-end through the packaged runners the harness uses: - RunMtpApplicationExecutesTestsOverMtpProtocol (2 pass/1 fail/1 skip) - RunMixedClassicAndMtpApplicationsInSingleRun (3/2/2) - RunMixedClassicAndMtpApplicationsWritesSingleTrx (3/2/2 + single TRX) Multi-target the MtpMSTestProject asset net8.0;net11.0 so the harness resolves net11.0 while manual E2E scripts keep net8.0. Register the asset in TestAssets.slnx so it builds in CI. All 6 matrix cells (3 tests x netfx+netcore console) pass locally through the real harness. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix TypesToLoadAttributeTests for the new MTP runtime provider The provider assembly registers MtpTestRuntimeProvider as a third TestExtensionTypes entry, so GetTypesToLoad returns three full names. The acceptance meta-test still listed two and failed 2 vs 3. Added the third expected name. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- eng/expected-dll-frameworks.json | 8 +- .../Hosting/ITestRuntimeProviderManager.cs | 9 + .../Hosting/TestRunTimeProviderManager.cs | 50 +- .../PublicAPI/PublicAPI.Shipped.txt | 2 +- .../MicrosoftTestingPlatformDetector.cs | 153 ++++++ .../Client/IProxyManagerFactory.cs | 43 ++ .../Client/MTP/MtpClientHelpers.cs | 97 ++++ .../Client/MTP/MtpConstants.cs | 75 +++ .../Client/MTP/MtpJson.cs | 57 ++ .../Client/MTP/MtpProxyDiscoveryManager.cs | 141 +++++ .../Client/MTP/MtpProxyExecutionManager.cs | 405 +++++++++++++++ .../Client/MTP/MtpProxyManagerFactory.cs | 38 ++ .../Client/MTP/MtpServerConnection.cs | 490 ++++++++++++++++++ .../Client/MTP/MtpTestNodeConverter.cs | 127 +++++ ...rosoft.TestPlatform.CrossPlatEngine.csproj | 2 +- .../PublicAPI/PublicAPI.Unshipped.txt | 6 + .../TestEngine.cs | 43 +- .../Friends.cs | 3 + .../Host/ISourceAwareTestRuntimeProvider.cs | 40 ++ .../Hosting/DefaultTestHostManager.cs | 6 +- .../Hosting/DotnetTestHostManager.cs | 8 +- .../Hosting/MtpTestRuntimeProvider.cs | 121 +++++ ...osoft.TestPlatform.TestHostProvider.csproj | 6 + .../Properties/AssemblyInfo.cs | 2 +- .../PublicAPI/PublicAPI.Unshipped.txt | 2 + .../CommandLine/AssemblyMetadataProvider.cs | 18 + .../Interfaces/IAssemblyMetadataProvider.cs | 7 + .../DiscoveryTests.cs | 2 +- .../MtpUnderVstestTests.cs | 99 ++++ .../Hosting/TestHostProviderManagerTests.cs | 136 +++++ .../MtpMSTestProject/MtpMSTestProject.csproj | 31 ++ test/TestAssets/MtpMSTestProject/UnitTests.cs | 39 ++ test/TestAssets/MtpPureProject/Calculator.cs | 38 ++ .../MtpPureProject/MtpPureProject.csproj | 40 ++ test/TestAssets/MtpPureProject/Program.cs | 24 + .../MtpPureProject/PureTestFramework.cs | 126 +++++ test/TestAssets/TestAssets.slnx | 1 + .../Fakes/FakeAssemblyMetadataProvider.cs | 2 + .../Fakes/FakeTestRuntimeProviderManager.cs | 2 + 39 files changed, 2481 insertions(+), 18 deletions(-) create mode 100644 src/Microsoft.TestPlatform.CoreUtilities/Helpers/MicrosoftTestingPlatformDetector.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Client/IProxyManagerFactory.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpClientHelpers.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpConstants.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpJson.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyDiscoveryManager.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyExecutionManager.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyManagerFactory.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpServerConnection.cs create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpTestNodeConverter.cs create mode 100644 src/Microsoft.TestPlatform.ObjectModel/Host/ISourceAwareTestRuntimeProvider.cs create mode 100644 src/Microsoft.TestPlatform.TestHostProvider/Hosting/MtpTestRuntimeProvider.cs create mode 100644 test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MtpUnderVstestTests.cs create mode 100644 test/TestAssets/MtpMSTestProject/MtpMSTestProject.csproj create mode 100644 test/TestAssets/MtpMSTestProject/UnitTests.cs create mode 100644 test/TestAssets/MtpPureProject/Calculator.cs create mode 100644 test/TestAssets/MtpPureProject/MtpPureProject.csproj create mode 100644 test/TestAssets/MtpPureProject/Program.cs create mode 100644 test/TestAssets/MtpPureProject/PureTestFramework.cs diff --git a/eng/expected-dll-frameworks.json b/eng/expected-dll-frameworks.json index 84f72e3b90..77af3cb9d8 100644 --- a/eng/expected-dll-frameworks.json +++ b/eng/expected-dll-frameworks.json @@ -182,7 +182,7 @@ "contentFiles/any/net10.0/Microsoft.Extensions.FileSystemGlobbing.dll": "netstandard", "contentFiles/any/net10.0/Microsoft.TestPlatform.CommunicationUtilities.dll": "net", "contentFiles/any/net10.0/Microsoft.TestPlatform.CoreUtilities.dll": "net", - "contentFiles/any/net10.0/Microsoft.TestPlatform.CrossPlatEngine.dll": "netstandard", + "contentFiles/any/net10.0/Microsoft.TestPlatform.CrossPlatEngine.dll": "net", "contentFiles/any/net10.0/Microsoft.TestPlatform.PlatformAbstractions.dll": "net", "contentFiles/any/net10.0/Microsoft.TestPlatform.Utilities.dll": "netstandard", "contentFiles/any/net10.0/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.dll": "net", @@ -379,7 +379,7 @@ "tools/net8.0/Microsoft.Extensions.FileSystemGlobbing.dll": "netstandard", "tools/net8.0/Microsoft.TestPlatform.CommunicationUtilities.dll": "net", "tools/net8.0/Microsoft.TestPlatform.CoreUtilities.dll": "net", - "tools/net8.0/Microsoft.TestPlatform.CrossPlatEngine.dll": "netstandard", + "tools/net8.0/Microsoft.TestPlatform.CrossPlatEngine.dll": "net", "tools/net8.0/Microsoft.TestPlatform.PlatformAbstractions.dll": "net", "tools/net8.0/Microsoft.TestPlatform.Utilities.dll": "netstandard", "tools/net8.0/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.dll": "net", @@ -391,7 +391,7 @@ "tools/net8.0/ru/Microsoft.CodeCoverage.IO.dll": "none", "tools/net8.0/TestHostNetFramework/Microsoft.TestPlatform.CommunicationUtilities.dll": "netframework", "tools/net8.0/TestHostNetFramework/Microsoft.TestPlatform.CoreUtilities.dll": "net", - "tools/net8.0/TestHostNetFramework/Microsoft.TestPlatform.CrossPlatEngine.dll": "netstandard", + "tools/net8.0/TestHostNetFramework/Microsoft.TestPlatform.CrossPlatEngine.dll": "net", "tools/net8.0/TestHostNetFramework/Microsoft.TestPlatform.PlatformAbstractions.dll": "net", "tools/net8.0/TestHostNetFramework/Microsoft.TestPlatform.Utilities.dll": "netstandard", "tools/net8.0/TestHostNetFramework/Microsoft.VisualStudio.TestPlatform.Common.dll": "netstandard", @@ -408,7 +408,7 @@ "build/net8.0/x86/testhost.x86.dll": "net", "lib/net8.0/Microsoft.TestPlatform.CommunicationUtilities.dll": "net", "lib/net8.0/Microsoft.TestPlatform.CoreUtilities.dll": "net", - "lib/net8.0/Microsoft.TestPlatform.CrossPlatEngine.dll": "netstandard", + "lib/net8.0/Microsoft.TestPlatform.CrossPlatEngine.dll": "net", "lib/net8.0/Microsoft.TestPlatform.PlatformAbstractions.dll": "net", "lib/net8.0/Microsoft.TestPlatform.Utilities.dll": "netstandard", "lib/net8.0/Microsoft.VisualStudio.TestPlatform.Common.dll": "netstandard", diff --git a/src/Microsoft.TestPlatform.Common/Hosting/ITestRuntimeProviderManager.cs b/src/Microsoft.TestPlatform.Common/Hosting/ITestRuntimeProviderManager.cs index f57666d6f1..856ac8fcb2 100644 --- a/src/Microsoft.TestPlatform.Common/Hosting/ITestRuntimeProviderManager.cs +++ b/src/Microsoft.TestPlatform.Common/Hosting/ITestRuntimeProviderManager.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Host; @@ -11,4 +12,12 @@ internal interface ITestRuntimeProviderManager { ITestRuntimeProvider? GetTestHostManagerByRunConfiguration(string? runConfiguration, List sources); ITestRuntimeProvider? GetTestHostManagerByUri(string hostUri); + + /// + /// Runs only the source-aware first-refusal pass for a single source and returns the of + /// the runtime provider that would claim it, without instantiating the provider. Returns + /// when no source-aware provider claims the source (i.e. it will be resolved by a generic, source-blind + /// provider). This lets callers group sources by which source-aware provider owns them. + /// + Type? GetSourceAwareRuntimeProviderType(string? runConfiguration, string source); } diff --git a/src/Microsoft.TestPlatform.Common/Hosting/TestRunTimeProviderManager.cs b/src/Microsoft.TestPlatform.Common/Hosting/TestRunTimeProviderManager.cs index 4090bdcfd6..40f6b0509d 100644 --- a/src/Microsoft.TestPlatform.Common/Hosting/TestRunTimeProviderManager.cs +++ b/src/Microsoft.TestPlatform.Common/Hosting/TestRunTimeProviderManager.cs @@ -43,10 +43,58 @@ public static TestRuntimeProviderManager Instance return host?.Value; } - public virtual ITestRuntimeProvider? GetTestHostManagerByRunConfiguration(string? runConfiguration, List? _) + Type? ITestRuntimeProviderManager.GetSourceAwareRuntimeProviderType(string? runConfiguration, string source) { + // Consult only the source-aware providers (the first-refusal pass), mirroring the first loop of + // GetTestHostManagerByRunConfiguration, but for a single source and without instantiating anything. + // Callers use the returned type purely as a grouping discriminator, so a source claimed by a + // source-aware provider is scheduled separately from a generic (source-blind) source. + var sources = new List { source }; foreach (var testExtension in _testHostExtensionManager.TestExtensions) { + if (testExtension.Value is ISourceAwareTestRuntimeProvider sourceAware + && sourceAware.CanExecuteCurrentRunConfiguration(runConfiguration, sources)) + { + return testExtension.Value.GetType(); + } + } + + return null; + } + + public virtual ITestRuntimeProvider? GetTestHostManagerByRunConfiguration(string? runConfiguration, List? sources) + { + // First pass: give source-aware providers first refusal. These providers (e.g. the + // Microsoft.Testing.Platform provider) can inspect the actual sources to decide whether they own the + // run, so they must be consulted before the generic, source-blind providers that match only by target + // framework. This gives the more specific provider priority without any global ordering scheme, and + // without relying on the generic providers to decline. + if (sources is not null && sources.Count > 0) + { + foreach (var testExtension in _testHostExtensionManager.TestExtensions) + { + if (testExtension.Value is ISourceAwareTestRuntimeProvider sourceAware + && sourceAware.CanExecuteCurrentRunConfiguration(runConfiguration, sources)) + { + // We are creating a new instance of ITestRuntimeProvider so that each POM gets its own object of ITestRuntimeProvider. + return (ITestRuntimeProvider?)Activator.CreateInstance(testExtension.Value.GetType()); + } + } + } + + // Second pass: the legacy, source-blind resolution based purely on the run configuration. + // Source-aware providers already had their (source-based) first refusal above, so exclude them here. + // Re-consulting them would be redundant, and — more importantly — a provider that declined by source + // must not be re-admitted by matching only the target framework. Enforcing the exclusion in the manager + // keeps the "first refusal" contract here, instead of relying on every source-aware provider to remember + // to return false when asked the source-blind question. + foreach (var testExtension in _testHostExtensionManager.TestExtensions) + { + if (testExtension.Value is ISourceAwareTestRuntimeProvider) + { + continue; + } + if (testExtension.Value.CanExecuteCurrentRunConfiguration(runConfiguration)) { // we are creating a new Instance of ITestRuntimeProvider so that each POM gets it's own object of ITestRuntimeProvider diff --git a/src/Microsoft.TestPlatform.Common/PublicAPI/PublicAPI.Shipped.txt b/src/Microsoft.TestPlatform.Common/PublicAPI/PublicAPI.Shipped.txt index b0f38dbb28..d2be8ce5dc 100644 --- a/src/Microsoft.TestPlatform.Common/PublicAPI/PublicAPI.Shipped.txt +++ b/src/Microsoft.TestPlatform.Common/PublicAPI/PublicAPI.Shipped.txt @@ -262,6 +262,6 @@ static Microsoft.VisualStudio.TestPlatform.Common.Utilities.RunSettingsUtilities virtual Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.TestPluginCache.GetFilteredExtensions(System.Collections.Generic.List! extensions, string! endsWithPattern) -> System.Collections.Generic.IEnumerable! virtual Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities.TestPluginInformation.IdentifierData.get -> string? virtual Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework.Utilities.TestPluginInformation.Metadata.get -> System.Collections.Generic.ICollection! -virtual Microsoft.VisualStudio.TestPlatform.Common.Hosting.TestRuntimeProviderManager.GetTestHostManagerByRunConfiguration(string? runConfiguration, System.Collections.Generic.List? _) -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Host.ITestRuntimeProvider? +virtual Microsoft.VisualStudio.TestPlatform.Common.Hosting.TestRuntimeProviderManager.GetTestHostManagerByRunConfiguration(string? runConfiguration, System.Collections.Generic.List? sources) -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Host.ITestRuntimeProvider? Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyDiscoveryManager.InitializeDiscovery(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.DiscoveryCriteria! discoveryCriteria, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestDiscoveryEventsHandler2! eventHandler, bool skipDefaultAdapters) -> void Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyExecutionManager.InitializeTestRun(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestRunCriteria! testRunCriteria, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IInternalTestRunEventsHandler! eventHandler) -> void diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Helpers/MicrosoftTestingPlatformDetector.cs b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/MicrosoftTestingPlatformDetector.cs new file mode 100644 index 0000000000..939b3c378b --- /dev/null +++ b/src/Microsoft.TestPlatform.CoreUtilities/Helpers/MicrosoftTestingPlatformDetector.cs @@ -0,0 +1,153 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; + +namespace Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers; + +/// +/// Detects whether an assembly is a Microsoft.Testing.Platform application by reading the +/// [assembly: AssemblyMetadata("Microsoft.Testing.Platform.Application", "true")] attribute +/// that the Microsoft.Testing.Platform MSBuild targets stamp onto the entry assembly at build time. +/// +/// +/// This lives in CoreUtilities so both the up-front detection in vstest.console and the routing +/// decision in the CrossPlatEngine TestEngine can share the exact same logic instead of duplicating +/// the (subtle) custom-attribute blob parsing. +/// +internal static class MicrosoftTestingPlatformDetector +{ + private const string MicrosoftTestingPlatformApplicationMetadataKey = "Microsoft.Testing.Platform.Application"; + + // Detection reads the assembly's PE metadata from disk, and a single run can ask about the same source + // several times (grouping, provider resolution, and per-source proxy creation). Memoize per source path so + // we read each assembly at most once. Concurrent because resolution can happen on parallel proxy threads. + private static readonly ConcurrentDictionary Cache = new(StringComparer.OrdinalIgnoreCase); + + /// + /// Returns if the assembly at is a + /// Microsoft.Testing.Platform application. Never throws; returns on any error. + /// + public static bool IsMicrosoftTestingPlatformApp(string filePath) + { + if (filePath is null) + { + return false; + } + + return Cache.GetOrAdd(filePath, static path => + { + try + { + using var assemblyStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + var result = IsMicrosoftTestingPlatformApp(assemblyStream); + EqtTrace.Info("MicrosoftTestingPlatformDetector.IsMicrosoftTestingPlatformApp: '{0}' for source: '{1}'", result, path); + return result; + } + catch (Exception ex) + { + EqtTrace.Warning("MicrosoftTestingPlatformDetector.IsMicrosoftTestingPlatformApp: failed to read assembly metadata, exception: {0} for assembly: {1}", ex, path); + return false; + } + }); + } + + /// + /// Returns if is a Microsoft.Testing.Platform + /// application. The caller owns the stream lifetime. + /// + public static bool IsMicrosoftTestingPlatformApp(Stream assemblyStream) + { + using var peReader = new PEReader(assemblyStream); + if (!peReader.HasMetadata) + { + return false; + } + + var metadataReader = peReader.GetMetadataReader(); + + // Microsoft.Testing.Platform applications are marked at build time with + // [assembly: AssemblyMetadata("Microsoft.Testing.Platform.Application", "true")] by the + // Microsoft.Testing.Platform MSBuild targets. We only look at assembly-level attributes. + foreach (var handle in metadataReader.GetAssemblyDefinition().GetCustomAttributes()) + { + var attribute = metadataReader.GetCustomAttribute(handle); + if (!IsAssemblyMetadataAttribute(metadataReader, attribute)) + { + continue; + } + + try + { + // AssemblyMetadataAttribute has a (string key, string value) constructor. The custom attribute + // blob is: 2-byte prolog (0x0001), then the two serialized strings, then the named-argument count. + var blob = metadataReader.GetBlobReader(attribute.Value); + if (blob.ReadUInt16() != 1) + { + continue; + } + + var key = blob.ReadSerializedString(); + var value = blob.ReadSerializedString(); + if (string.Equals(key, MicrosoftTestingPlatformApplicationMetadataKey, StringComparison.Ordinal) + && string.Equals(value, "true", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + catch (Exception ex) + { + EqtTrace.Verbose("MicrosoftTestingPlatformDetector.IsMicrosoftTestingPlatformApp: could not decode AssemblyMetadata attribute: {0}", ex); + } + } + + return false; + } + + private static bool IsAssemblyMetadataAttribute(MetadataReader metadataReader, CustomAttribute attribute) + { + StringHandle typeNameHandle; + StringHandle typeNamespaceHandle; + switch (attribute.Constructor.Kind) + { + case HandleKind.MemberReference: + var memberReference = metadataReader.GetMemberReference((MemberReferenceHandle)attribute.Constructor); + switch (memberReference.Parent.Kind) + { + case HandleKind.TypeReference: + var typeReference = metadataReader.GetTypeReference((TypeReferenceHandle)memberReference.Parent); + typeNameHandle = typeReference.Name; + typeNamespaceHandle = typeReference.Namespace; + break; + case HandleKind.TypeDefinition: + var typeDefinition = metadataReader.GetTypeDefinition((TypeDefinitionHandle)memberReference.Parent); + typeNameHandle = typeDefinition.Name; + typeNamespaceHandle = typeDefinition.Namespace; + break; + default: + return false; + } + + break; + + case HandleKind.MethodDefinition: + var methodDefinition = metadataReader.GetMethodDefinition((MethodDefinitionHandle)attribute.Constructor); + var declaringType = metadataReader.GetTypeDefinition(methodDefinition.GetDeclaringType()); + typeNameHandle = declaringType.Name; + typeNamespaceHandle = declaringType.Namespace; + break; + + default: + return false; + } + + return string.Equals(metadataReader.GetString(typeNameHandle), "AssemblyMetadataAttribute", StringComparison.Ordinal) + && string.Equals(metadataReader.GetString(typeNamespaceHandle), "System.Reflection", StringComparison.Ordinal); + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/IProxyManagerFactory.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/IProxyManagerFactory.cs new file mode 100644 index 0000000000..ad355d646c --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/IProxyManagerFactory.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; + +/// +/// Implemented by a runtime provider (an ) that hosts a +/// run over its own protocol instead of the vstest testhost protocol, and therefore supplies its own +/// discovery/execution proxy managers. +/// +/// +/// The standard flow creates a ProxyDiscoveryManager/ProxyExecutionManager that wrap an +/// launching a vstest testhost. Some providers — such as +/// the Microsoft.Testing.Platform provider — are fundamentally a different shape: the test application is its +/// own host and speaks its own JSON-RPC protocol, so it needs a different proxy manager entirely. Rather than +/// teaching about each such protocol with inline branches, the resolved provider that +/// implements this interface is asked to produce its own proxy managers. This keeps protocol-specific wiring +/// in the provider and out of the engine. +/// +/// This interface is public because the runtime providers that implement it live in a separate assembly +/// (Microsoft.TestPlatform.TestHostRuntimeProvider). The concrete proxy managers stay internal to this +/// assembly; providers create them through the public helper. +/// +/// +public interface IProxyManagerFactory +{ + /// + /// Creates the discovery manager used to drive discovery for this provider's sources. + /// + IProxyDiscoveryManager CreateDiscoveryManager(); + + /// + /// Creates the execution manager used to drive execution for this provider's sources. + /// + /// + /// The data collection manager to wire in when data collectors are enabled, or + /// when data collection is off. + /// + IProxyExecutionManager CreateExecutionManager(IProxyDataCollectionManager? dataCollectionManager); +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpClientHelpers.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpClientHelpers.cs new file mode 100644 index 0000000000..83ef3bdcef --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpClientHelpers.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +using Jsonite; + +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.MTP; + +/// +/// Shared helpers for the MTP proxies. +/// +internal static class MtpClientHelpers +{ + public static Dictionary InitializeParameters() + => new() + { + ["processId"] = GetCurrentProcessId(), + ["clientInfo"] = new Dictionary + { + ["name"] = "vstest", + ["version"] = "1.0.0", + }, + ["capabilities"] = new Dictionary + { + ["testing"] = new Dictionary + { + ["debuggerProvider"] = false, + }, + }, + }; + + public static TestMessageLevel MapLevel(string level) + => level switch + { + "Error" or "Critical" => TestMessageLevel.Error, + "Warning" => TestMessageLevel.Warning, + _ => TestMessageLevel.Informational, + }; + + public static TimeSpan GetConnectionTimeout() + { + // Reuse vstest's connection timeout knob so users can extend it in slow environments. + string? value = Environment.GetEnvironmentVariable("VSTEST_CONNECTION_TIMEOUT"); + if (!string.IsNullOrEmpty(value) && int.TryParse(value, out int seconds) && seconds > 0) + { + return TimeSpan.FromSeconds(seconds); + } + + return TimeSpan.FromSeconds(90); + } + + private static int GetCurrentProcessId() + { + using var process = Process.GetCurrentProcess(); + return process.Id; + } + + /// + /// Returns true when a testing/testUpdates/tests notification is the completion sentinel + /// (its changes array is null or absent). + /// + public static bool IsCompletionSentinel(object? parameters) + { + JsonObject? node = MtpJson.AsObject(parameters); + return node is null + || !node.TryGetValue(MtpConstants.ChangesProperty, out object? changes) + || changes is null; + } + + /// + /// Enumerates the node objects carried by a testing/testUpdates/tests notification. + /// + public static IEnumerable EnumerateNodes(object? parameters) + { + if (MtpJson.AsObject(parameters) is not JsonObject node + || !node.TryGetValue(MtpConstants.ChangesProperty, out object? changesValue) + || changesValue is not JsonArray changes) + { + yield break; + } + + foreach (object? changeObject in changes) + { + if (changeObject is JsonObject change + && change.TryGetValue(MtpConstants.NodeProperty, out object? nodeValue) + && nodeValue is JsonObject testNode) + { + yield return testNode; + } + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpConstants.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpConstants.cs new file mode 100644 index 0000000000..1189fc3fec --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpConstants.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.MTP; + +/// +/// Constants for the Microsoft.Testing.Platform (MTP) server-mode JSON-RPC protocol. +/// See the MTP protocol docs in microsoft/testfx (ServerMode/JsonRpc). +/// +internal static class MtpConstants +{ + // Command line used to start an MTP application in JSON-RPC server mode. vstest opens a TCP + // listener and the application connects back to it (the application dials out to us). + public const string ServerArgument = "--server"; + public const string ClientPortArgument = "--client-port"; + public const string NoBannerArgument = "--no-banner"; + + // JSON-RPC method names. + public const string InitializeMethod = "initialize"; + public const string DiscoverTestsMethod = "testing/discoverTests"; + public const string RunTestsMethod = "testing/runTests"; + public const string TestUpdatesTestsMethod = "testing/testUpdates/tests"; + public const string TestUpdatesAttachmentsMethod = "testing/testUpdates/attachments"; + public const string ClientLogMethod = "client/log"; + public const string ExitMethod = "exit"; + + // Framing (LSP-like headers). + public const string ContentLengthHeader = "Content-Length:"; + public const string ContentType = "application/testingplatform"; + + // Request/notification parameter keys. + public const string RunIdParameter = "runId"; + public const string TestsParameter = "tests"; + public const string ChangesProperty = "changes"; + public const string NodeProperty = "node"; + public const string AttachmentsProperty = "attachments"; + public const string AttachmentUriProperty = "uri"; + public const string AttachmentPathProperty = "path"; + + // TestNode wire property keys (pure MTP shape). + public const string Uid = "uid"; + public const string DisplayName = "display-name"; + public const string NodeType = "node-type"; + public const string ExecutionState = "execution-state"; + public const string TimeDurationMs = "time.duration-ms"; + public const string ErrorMessage = "error.message"; + public const string ErrorStackTrace = "error.stacktrace"; + public const string LocationFile = "location.file"; + public const string LocationLineStart = "location.line-start"; + public const string Traits = "traits"; + + // Execution states. + public const string StateDiscovered = "discovered"; + public const string StateInProgress = "in-progress"; + public const string StatePassed = "passed"; + public const string StateSkipped = "skipped"; + public const string StateFailed = "failed"; + public const string StateError = "error"; + public const string StateTimedOut = "timed-out"; + public const string StateCanceled = "canceled"; + + // Optional VSTest-provider properties (present only when the app still runs on the VSTestBridge). + // The converter treats these as best-effort enrichment and never requires them, so that a pure + // MTP app with no vstest dependency at all still converts correctly. + public const string VsTestFullyQualifiedName = "vstest.TestCase.FullyQualifiedName"; + public const string VsTestId = "vstest.TestCase.Id"; + public const string VsTestExecutorUri = "vstest.original-executor-uri"; + + // Synthetic executor URI used when the app does not expose the vstest provider properties. + public const string DefaultExecutorUri = "executor://MicrosoftTestingPlatform/v1"; + + // Property used to round-trip the MTP node uid on a vstest TestCase so we can request a + // filtered run by uid after discovery. + public const string MtpUidPropertyId = "MTP.TestNode.Uid"; +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpJson.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpJson.cs new file mode 100644 index 0000000000..038a77b397 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpJson.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Jsonite; + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.MTP; + +/// +/// Small accessors over the Jsonite JSON object model used by the MTP JSON-RPC client. +/// +/// The MTP wire is serialized with Jsonite (not System.Text.Json) so that the client works on every +/// framework we ship — including the .NET Framework runner, where taking a dependency on +/// System.Text.Json would introduce binding-redirect fallout in hosts that run without them. Parsed +/// JSON is a plain object graph: objects are (a +/// Dictionary<string, object>), arrays are (a +/// List<object>), numbers are int/long/double, and everything else +/// is string/bool/null. +/// +internal static class MtpJson +{ + public static JsonObject? AsObject(object? node) => node as JsonObject; + + public static JsonArray? AsArray(object? node) => node as JsonArray; + + public static object? GetValue(JsonObject? node, string key) + => node is not null && node.TryGetValue(key, out object? value) ? value : null; + + public static string? GetString(JsonObject? node, string key) + => GetValue(node, key) as string; + + public static bool TryGetInt(JsonObject? node, string key, out int result) + => TryToInt(GetValue(node, key), out result); + + public static bool TryGetDouble(JsonObject? node, string key, out double result) + { + switch (GetValue(node, key)) + { + case double d: result = d; return true; + case int i: result = i; return true; + case long l: result = l; return true; + case decimal m: result = (double)m; return true; + default: result = 0; return false; + } + } + + public static bool TryToInt(object? value, out int result) + { + switch (value) + { + case int i: result = i; return true; + case long l: result = unchecked((int)l); return true; + case double d: result = (int)d; return true; + case decimal m: result = (int)m; return true; + default: result = 0; return false; + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyDiscoveryManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyDiscoveryManager.cs new file mode 100644 index 0000000000..07a36ed168 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyDiscoveryManager.cs @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +using Jsonite; + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.MTP; + +/// +/// An that discovers tests by driving a +/// Microsoft.Testing.Platform (MTP) application over the MTP JSON-RPC protocol instead of the +/// vstest testhost protocol. +/// +internal sealed class MtpProxyDiscoveryManager : IProxyDiscoveryManager, IDisposable +{ + private readonly CancellationTokenSource _cancellationTokenSource = new(); + + public void Initialize(bool skipDefaultAdapters) + { + } + + public void InitializeDiscovery(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEventsHandler2 eventHandler, bool skipDefaultAdapters) + => Initialize(skipDefaultAdapters); + + public void DiscoverTests(DiscoveryCriteria discoveryCriteria, ITestDiscoveryEventsHandler2 eventHandler) + { + var sources = discoveryCriteria.Sources?.ToList() ?? new List(); + long totalTests = 0; + bool aborted = false; + + foreach (string source in sources) + { + if (_cancellationTokenSource.IsCancellationRequested) + { + aborted = true; + break; + } + + try + { + totalTests += DiscoverSource(source, eventHandler); + } + catch (OperationCanceledException) + { + aborted = true; + break; + } + catch (Exception ex) + { + EqtTrace.Error("MtpProxyDiscoveryManager.DiscoverTests: discovery failed for '{0}': {1}", source, ex); + eventHandler.HandleLogMessage(ObjectModel.Logging.TestMessageLevel.Error, $"Microsoft.Testing.Platform discovery failed for '{source}': {ex.Message}"); + aborted = true; + } + } + + eventHandler.HandleDiscoveryComplete(new DiscoveryCompleteEventArgs(totalTests, aborted), null); + } + + public void Abort() => _cancellationTokenSource.Cancel(); + + public void Abort(ITestDiscoveryEventsHandler2 eventHandler) => Abort(); + + public void Close() => _cancellationTokenSource.Cancel(); + + public void Dispose() + { + try + { + _cancellationTokenSource.Dispose(); + } + catch + { + // ignore + } + } + + private int DiscoverSource(string source, ITestDiscoveryEventsHandler2 eventHandler) + { + var discovered = new List(); + var completed = new ManualResetEventSlim(false); + + using var connection = new MtpServerConnection(); + connection.LogReceived += (level, message) => eventHandler.HandleLogMessage(MtpClientHelpers.MapLevel(level), message); + connection.TestNodesUpdated += parameters => + { + if (MtpClientHelpers.IsCompletionSentinel(parameters)) + { + completed.Set(); + return; + } + + foreach (JsonObject node in MtpClientHelpers.EnumerateNodes(parameters)) + { + if (MtpTestNodeConverter.IsActionNode(node)) + { + lock (discovered) + { + discovered.Add(MtpTestNodeConverter.ToTestCase(node, source)); + } + } + } + }; + + connection.Start(source, environmentVariables: null, MtpClientHelpers.GetConnectionTimeout()); + connection.InvokeAsync(MtpConstants.InitializeMethod, MtpClientHelpers.InitializeParameters(), _cancellationTokenSource.Token).GetAwaiter().GetResult(); + + var runId = Guid.NewGuid(); + var discoverTask = connection.InvokeAsync( + MtpConstants.DiscoverTestsMethod, + new Dictionary { [MtpConstants.RunIdParameter] = runId.ToString() }, + _cancellationTokenSource.Token); + + // The response indicates the server has finished discovery. Because messages arrive on a + // single ordered stream that we read sequentially, every node notification sent before the + // response has already been dispatched by the time the response completes. + discoverTask.GetAwaiter().GetResult(); + completed.Wait(TimeSpan.FromSeconds(3)); + + List chunk; + lock (discovered) + { + chunk = discovered.ToList(); + } + + if (chunk.Count > 0) + { + eventHandler.HandleDiscoveredTests(chunk); + } + + connection.SendNotification(MtpConstants.ExitMethod, null); + return chunk.Count; + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyExecutionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyExecutionManager.cs new file mode 100644 index 0000000000..fc4363a832 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyExecutionManager.cs @@ -0,0 +1,405 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Threading; + +using Jsonite; + +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection; +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.MTP; + +/// +/// An that runs tests by driving a Microsoft.Testing.Platform +/// (MTP) application over the MTP JSON-RPC protocol instead of the vstest testhost protocol. +/// +internal sealed class MtpProxyExecutionManager : IProxyExecutionManager, IDisposable +{ + private readonly CancellationTokenSource _cancellationTokenSource = new(); + + /// + /// Optional data collection manager (e.g. code coverage). When present, it is started before the + /// run to obtain profiler environment variables that are injected into the MTP application, is + /// notified of the MTP application's process id, and is asked for its attachments (such as the + /// .coverage file) once the run completes. + /// + private readonly IProxyDataCollectionManager? _dataCollectionManager; + + private readonly DataCollectionRunEventsHandler? _dataCollectionEventsHandler; + + private bool _isInitialized; + + public MtpProxyExecutionManager() + { + } + + public MtpProxyExecutionManager(IProxyDataCollectionManager dataCollectionManager) + { + _dataCollectionManager = dataCollectionManager; + _dataCollectionEventsHandler = new DataCollectionRunEventsHandler(); + } + + public bool IsInitialized => _isInitialized; + + /// + /// Environment variables to inject into the MTP application process. Used to pass code coverage + /// profiler settings supplied by the data collector. + /// + public IDictionary? EnvironmentVariables { get; set; } + + public void Initialize(bool skipDefaultAdapters) => _isInitialized = true; + + public void InitializeTestRun(TestRunCriteria testRunCriteria, IInternalTestRunEventsHandler eventHandler) + => Initialize(skipDefaultAdapters: true); + + public int StartTestRun(TestRunCriteria testRunCriteria, IInternalTestRunEventsHandler eventHandler) + { + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + var aggregate = new RunAggregate(); + var attachments = new List(); + var executorUris = new HashSet(StringComparer.OrdinalIgnoreCase); + var invokedDataCollectors = new List(); + int processId = 0; + bool aborted = false; + + BeforeTestRun(eventHandler); + + foreach (var (source, tests) in BuildWork(testRunCriteria)) + { + if (_cancellationTokenSource.IsCancellationRequested) + { + break; + } + + try + { + processId = RunSource(source, tests, eventHandler, aggregate, attachments, executorUris); + } + catch (OperationCanceledException) + { + break; + } + catch (Exception ex) + { + EqtTrace.Error("MtpProxyExecutionManager.StartTestRun: run failed for '{0}': {1}", source, ex); + eventHandler.HandleLogMessage(ObjectModel.Logging.TestMessageLevel.Error, $"Microsoft.Testing.Platform run failed for '{source}': {ex.Message}"); + aborted = true; + } + } + + AfterTestRun(attachments, invokedDataCollectors); + + TestRunStatistics finalStats = aggregate.Snapshot(); + var completeArgs = new TestRunCompleteEventArgs( + finalStats, + _cancellationTokenSource.IsCancellationRequested, + aborted, + null, + new Collection(attachments), + stopwatch.Elapsed); + + foreach (InvokedDataCollector collector in invokedDataCollectors) + { + completeArgs.InvokedDataCollectors.Add(collector); + } + + eventHandler.HandleTestRunComplete(completeArgs, null, attachments, executorUris.ToList()); + return processId; + } + + /// + /// Starts the data collector (if any) before the run and injects the profiler environment + /// variables it produces into the MTP application launch. Any messages the data collector logged + /// during startup are forwarded to the run events handler. + /// + private void BeforeTestRun(IInternalTestRunEventsHandler eventHandler) + { + if (_dataCollectionManager is null) + { + return; + } + + _dataCollectionManager.Initialize(); + + DataCollectionParameters parameters; + try + { + parameters = _dataCollectionManager.BeforeTestRunStart( + resetDataCollectors: true, + isRunStartingNow: true, + runEventsHandler: _dataCollectionEventsHandler!); + } + catch (Exception) + { + _dataCollectionManager.AfterTestRunEnd(isCanceled: true, runEventsHandler: _dataCollectionEventsHandler!); + throw; + } + + if (parameters?.EnvironmentVariables is { } dataCollectionEnvironmentVariables) + { + EnvironmentVariables ??= new Dictionary(); + foreach (KeyValuePair variable in dataCollectionEnvironmentVariables) + { + EnvironmentVariables[variable.Key] = variable.Value; + } + } + + // Surface any messages the data collector produced while starting up. + foreach (Tuple message in _dataCollectionEventsHandler!.Messages) + { + eventHandler.HandleLogMessage(message.Item1, message.Item2); + } + + _dataCollectionEventsHandler.Messages.Clear(); + } + + /// + /// Ends the data collector (if any) after the run and collects its attachments (such as the + /// .coverage file) and the list of invoked data collectors. + /// + private void AfterTestRun(List attachments, List invokedDataCollectors) + { + if (_dataCollectionManager is null) + { + return; + } + + DataCollectionResult result = _dataCollectionManager.AfterTestRunEnd( + _cancellationTokenSource.IsCancellationRequested, + _dataCollectionEventsHandler!); + + if (result.Attachments is { Count: > 0 }) + { + lock (attachments) + { + attachments.AddRange(result.Attachments); + } + } + + if (result.InvokedDataCollectors is { Count: > 0 }) + { + invokedDataCollectors.AddRange(result.InvokedDataCollectors); + } + } + + public void Cancel(IInternalTestRunEventsHandler eventHandler) => _cancellationTokenSource.Cancel(); + + public void Abort(IInternalTestRunEventsHandler eventHandler) => _cancellationTokenSource.Cancel(); + + public void Close() => _cancellationTokenSource.Cancel(); + + public void Dispose() + { + try + { + _dataCollectionManager?.Dispose(); + } + catch + { + // ignore + } + + try + { + _cancellationTokenSource.Dispose(); + } + catch + { + // ignore + } + } + + private int RunSource( + string source, + List? tests, + IInternalTestRunEventsHandler eventHandler, + RunAggregate aggregate, + List attachments, + HashSet executorUris) + { + var completed = new ManualResetEventSlim(false); + + using var connection = new MtpServerConnection(); + connection.LogReceived += (level, message) => eventHandler.HandleLogMessage(MtpClientHelpers.MapLevel(level), message); + connection.TestNodesUpdated += parameters => + { + if (MtpClientHelpers.IsCompletionSentinel(parameters)) + { + completed.Set(); + return; + } + + var results = new List(); + foreach (JsonObject node in MtpClientHelpers.EnumerateNodes(parameters)) + { + if (!MtpTestNodeConverter.IsActionNode(node)) + { + continue; + } + + if (!MtpTestNodeConverter.IsTerminalState(MtpTestNodeConverter.GetExecutionState(node))) + { + continue; + } + + results.Add(MtpTestNodeConverter.ToTestResult(node, source)); + } + + if (results.Count == 0) + { + return; + } + + TestRunStatistics snapshot; + lock (aggregate.Lock) + { + foreach (TestResult result in results) + { + aggregate.Add(result); + if (result.TestCase.ExecutorUri is { } uri) + { + executorUris.Add(uri.ToString()); + } + } + + snapshot = aggregate.Snapshot(); + } + + eventHandler.HandleTestRunStatsChange(new TestRunChangedEventArgs(snapshot, results, null)); + }; + + connection.Start(source, EnvironmentVariables, MtpClientHelpers.GetConnectionTimeout()); + + // Let the data collector (e.g. code coverage) know the process it should track. The profiler + // env vars were already injected via EnvironmentVariables above. + _dataCollectionManager?.TestHostLaunched(connection.ProcessId); + + connection.InvokeAsync(MtpConstants.InitializeMethod, MtpClientHelpers.InitializeParameters(), _cancellationTokenSource.Token).GetAwaiter().GetResult(); + + var runId = Guid.NewGuid(); + var runParameters = new Dictionary { [MtpConstants.RunIdParameter] = runId.ToString() }; + if (tests is { Count: > 0 }) + { + runParameters[MtpConstants.TestsParameter] = BuildTestsFilter(tests); + } + + var runTask = connection.InvokeAsync(MtpConstants.RunTestsMethod, runParameters, _cancellationTokenSource.Token); + object? response = runTask.GetAwaiter().GetResult(); + completed.Wait(TimeSpan.FromSeconds(3)); + + CollectAttachments(response, attachments); + connection.SendNotification(MtpConstants.ExitMethod, null); + return connection.ProcessId; + } + + private static IEnumerable<(string Source, List? Tests)> BuildWork(TestRunCriteria criteria) + { + if (criteria.HasSpecificTests && criteria.Tests is not null) + { + return criteria.Tests + .GroupBy(test => test.Source) + .Select(group => (group.Key, (List?)group.ToList())); + } + + return (criteria.Sources ?? Enumerable.Empty()) + .Select(source => (source, (List?)null)); + } + + private static List> BuildTestsFilter(List tests) + => tests + .Select(test => new Dictionary + { + [MtpConstants.Uid] = test.GetPropertyValue(MtpTestNodeConverter.MtpUidProperty, test.FullyQualifiedName), + [MtpConstants.DisplayName] = test.DisplayName, + }) + .ToList(); + + private static void CollectAttachments(object? response, List attachments) + { + if (MtpJson.AsObject(response) is not JsonObject responseObject + || !responseObject.TryGetValue(MtpConstants.AttachmentsProperty, out object? attachmentsValue) + || attachmentsValue is not JsonArray attachmentArray) + { + return; + } + + var set = new AttachmentSet(new Uri(MtpConstants.DefaultExecutorUri), "Microsoft.Testing.Platform"); + foreach (object? attachmentObject in attachmentArray) + { + if (attachmentObject is not JsonObject attachment) + { + continue; + } + + string? path = GetStringProperty(attachment, MtpConstants.AttachmentUriProperty) + ?? GetStringProperty(attachment, MtpConstants.AttachmentPathProperty); + if (string.IsNullOrEmpty(path)) + { + continue; + } + + if (!TryCreateFileUri(path!, out Uri? fileUri)) + { + continue; + } + + string display = GetStringProperty(attachment, MtpConstants.DisplayName) ?? Path.GetFileName(path!); + set.Attachments.Add(new UriDataAttachment(fileUri!, display)); + } + + if (set.Attachments.Count > 0) + { + lock (attachments) + { + attachments.Add(set); + } + } + } + + private static string? GetStringProperty(JsonObject element, string name) + => element.TryGetValue(name, out object? value) && value is string text ? text : null; + + private static bool TryCreateFileUri(string path, out Uri? uri) + { + try + { + uri = Uri.TryCreate(path, UriKind.Absolute, out Uri? absolute) && absolute.IsFile + ? absolute + : new Uri(Path.GetFullPath(path)); + return true; + } + catch (Exception ex) when (ex is ArgumentException or UriFormatException or NotSupportedException or PathTooLongException) + { + uri = null; + return false; + } + } + + private sealed class RunAggregate + { + public object Lock { get; } = new(); + + private readonly Dictionary _byOutcome = new(); + private long _executed; + + public void Add(TestResult result) + { + _byOutcome.TryGetValue(result.Outcome, out long count); + _byOutcome[result.Outcome] = count + 1; + _executed++; + } + + public TestRunStatistics Snapshot() + => new(_executed, new Dictionary(_byOutcome)); + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyManagerFactory.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyManagerFactory.cs new file mode 100644 index 0000000000..5cb732d7c1 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyManagerFactory.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.MTP; + +/// +/// Creates the Microsoft.Testing.Platform (MTP) discovery/execution proxy managers. +/// +/// +/// The concrete proxy managers ( and ) +/// are internal to this assembly. The MTP runtime provider that drives them lives in a separate assembly +/// (Microsoft.TestPlatform.TestHostRuntimeProvider), so it cannot instantiate them directly; it goes +/// through this small public factory instead. This keeps the MTP proxy implementations internal while giving the +/// out-of-assembly provider a single, intentional public seam to create them. +/// +public static class MtpProxyManagerFactory +{ + /// + /// Creates the discovery manager that drives discovery for Microsoft.Testing.Platform sources. + /// + public static IProxyDiscoveryManager CreateDiscoveryManager() + => new MtpProxyDiscoveryManager(); + + /// + /// Creates the execution manager that drives execution for Microsoft.Testing.Platform sources. + /// + /// + /// The data collection manager to wire in when data collectors are enabled, or + /// when data collection is off. + /// + public static IProxyExecutionManager CreateExecutionManager(IProxyDataCollectionManager? dataCollectionManager) + => dataCollectionManager is null + ? new MtpProxyExecutionManager() + : new MtpProxyExecutionManager(dataCollectionManager); +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpServerConnection.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpServerConnection.cs new file mode 100644 index 0000000000..c4ac558b64 --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpServerConnection.cs @@ -0,0 +1,490 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using Jsonite; + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.MTP; + +/// +/// Manages a single Microsoft.Testing.Platform (MTP) application running in JSON-RPC server mode. +/// +/// vstest is the JSON-RPC client here: it opens a loopback TCP listener, launches the MTP +/// application with --server --client-port <port>, and the application connects back to +/// the listener. Messages are framed with LSP-style Content-Length headers. +/// +/// The wire is serialized with Jsonite rather than System.Text.Json so this client works on every +/// framework we ship, including the .NET Framework runner (no System.Text.Json dependency and thus +/// no binding-redirect fallout). Parsed messages are plain / object graphs. +/// +internal sealed class MtpServerConnection : IDisposable +{ + private readonly TcpListener _listener; + private readonly int _port; + private readonly ConcurrentDictionary> _pending = new(); + private readonly object _writeLock = new(); + private readonly CancellationTokenSource _cts = new(); + private readonly StringBuilder _standardError = new(); + + private TcpClient? _client; + private Stream? _stream; + private Process? _process; + private Task? _readLoop; + private int _nextId; + private bool _disposed; + + /// + /// Raised for each testing/testUpdates/tests notification. The argument is the parsed + /// notification params value (a ), or null when absent. + /// + public event Action? TestNodesUpdated; + + /// + /// Raised for each client/log notification with (level, message). + /// + public event Action? LogReceived; + + public MtpServerConnection() + { + _listener = new TcpListener(IPAddress.Loopback, 0); + _listener.Start(); + _port = ((IPEndPoint)_listener.LocalEndpoint).Port; + } + + /// + /// Gets the process id of the launched MTP application, or 0 if it has not been launched. + /// + public int ProcessId => _process?.Id ?? 0; + + /// + /// Launches the MTP application in server mode and waits for it to connect back. + /// + public void Start(string source, IDictionary? environmentVariables, TimeSpan connectionTimeout) + { + var (fileName, arguments, workingDirectory) = BuildLaunch(source, _port); + EqtTrace.Info("MtpServerConnection.Start: launching '{0} {1}' (cwd '{2}') listening on port {3}.", fileName, arguments, workingDirectory, _port); + + var startInfo = new ProcessStartInfo + { + FileName = fileName, + Arguments = arguments, + WorkingDirectory = workingDirectory, + UseShellExecute = false, + RedirectStandardError = true, + RedirectStandardOutput = true, + }; + + if (environmentVariables != null) + { + foreach (var kvp in environmentVariables) + { + startInfo.Environment[kvp.Key] = kvp.Value; + } + } + + _process = new Process { StartInfo = startInfo, EnableRaisingEvents = true }; + _process.ErrorDataReceived += (_, e) => + { + if (e.Data != null) + { + lock (_standardError) + { + _standardError.AppendLine(e.Data); + } + } + }; + _process.Start(); + _process.BeginErrorReadLine(); + // Drain stdout so the child never blocks on a full pipe (banner, diagnostics). + _process.BeginOutputReadLine(); + + var acceptTask = _listener.AcceptTcpClientAsync(); + if (!acceptTask.Wait(connectionTimeout)) + { + throw new TimeoutException($"The Microsoft.Testing.Platform application '{source}' did not connect back within {connectionTimeout.TotalSeconds:N0}s. {GetStandardError()}"); + } + + _client = acceptTask.GetAwaiter().GetResult(); + _client.NoDelay = true; + _stream = _client.GetStream(); + _readLoop = Task.Run(() => ReadLoopAsync(_cts.Token)); + } + + /// + /// Sends a JSON-RPC request and awaits the response. + /// + public async Task InvokeAsync(string method, object? parameters, CancellationToken cancellationToken) + { + int id = Interlocked.Increment(ref _nextId); + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _pending[id] = tcs; + + var envelope = new Dictionary + { + ["jsonrpc"] = "2.0", + ["id"] = id, + ["method"] = method, + ["params"] = parameters ?? new Dictionary(), + }; + + WriteMessage(envelope); + + using var registration = cancellationToken.Register(static state => ((TaskCompletionSource)state!).TrySetCanceled(), tcs); + try + { + return await tcs.Task.ConfigureAwait(false); + } + finally + { + _pending.TryRemove(id, out _); + } + } + + /// + /// Sends a JSON-RPC notification (no response expected). + /// + public void SendNotification(string method, object? parameters) + { + var envelope = new Dictionary + { + ["jsonrpc"] = "2.0", + ["method"] = method, + ["params"] = parameters ?? new Dictionary(), + }; + WriteMessage(envelope); + } + + public string GetStandardError() + { + lock (_standardError) + { + var text = _standardError.ToString().Trim(); + return text.Length == 0 ? string.Empty : $"Standard error: {text}"; + } + } + + private void WriteMessage(Dictionary envelope) + { + if (_stream is null) + { + throw new InvalidOperationException("MTP connection has not been established."); + } + + string json = Json.Serialize(envelope); + byte[] body = Encoding.UTF8.GetBytes(json); + byte[] header = Encoding.ASCII.GetBytes($"{MtpConstants.ContentLengthHeader} {body.Length}\r\nContent-Type: {MtpConstants.ContentType}\r\n\r\n"); + + lock (_writeLock) + { + _stream.Write(header, 0, header.Length); + _stream.Write(body, 0, body.Length); + _stream.Flush(); + } + } + + private async Task ReadLoopAsync(CancellationToken cancellationToken) + { + Debug.Assert(_stream != null, "Stream must be set before the read loop starts."); + try + { + while (!cancellationToken.IsCancellationRequested) + { + int contentLength = await ReadHeadersAsync(_stream!, cancellationToken).ConfigureAwait(false); + if (contentLength < 0) + { + break; // stream closed + } + + byte[] body = await ReadExactlyAsync(_stream!, contentLength, cancellationToken).ConfigureAwait(false); + if (body.Length < contentLength) + { + break; // stream closed mid-message + } + + Dispatch(body); + } + } + catch (OperationCanceledException) + { + } + catch (Exception ex) when (ex is IOException or ObjectDisposedException or SocketException) + { + // Connection closed; fail any pending requests below. + } + catch (Exception ex) + { + EqtTrace.Error("MtpServerConnection.ReadLoopAsync: unexpected error: {0}", ex); + } + finally + { + FailPending(new IOException($"The MTP connection was closed. {GetStandardError()}")); + } + } + + private void Dispatch(byte[] body) + { + object? parsed; + try + { + parsed = Json.Deserialize(Encoding.UTF8.GetString(body)); + } + catch (JsonException ex) + { + EqtTrace.Error("MtpServerConnection.Dispatch: failed to parse message: {0}", ex); + return; + } + + if (parsed is not JsonObject root) + { + return; + } + + int? id = root.TryGetValue("id", out object? idValue) && MtpJson.TryToInt(idValue, out int parsedId) + ? parsedId + : (int?)null; + + if (root.TryGetValue("method", out object? methodValue) && methodValue is string method) + { + object? parameters = root.TryGetValue("params", out object? p) ? p : null; + HandleServerMessage(method, parameters, id); + return; + } + + if (id is int responseId && _pending.TryGetValue(responseId, out var tcs)) + { + if (root.TryGetValue("error", out object? errorValue) && errorValue is JsonObject error) + { + string message = error.TryGetValue("message", out object? m) && m is string ms ? ms : "unknown error"; + tcs.TrySetException(new InvalidOperationException($"MTP request '{responseId}' failed: {message}")); + } + else + { + object? result = root.TryGetValue("result", out object? r) ? r : null; + tcs.TrySetResult(result); + } + } + } + + private void HandleServerMessage(string method, object? parameters, int? id) + { + switch (method) + { + case MtpConstants.TestUpdatesTestsMethod: + TestNodesUpdated?.Invoke(parameters); + break; + + case MtpConstants.ClientLogMethod: + JsonObject? logParams = MtpJson.AsObject(parameters); + string level = MtpJson.GetString(logParams, "level") ?? "Information"; + string message = MtpJson.GetString(logParams, "message") ?? string.Empty; + LogReceived?.Invoke(level, message); + break; + + default: + // Requests from the server (e.g. client/attachDebugger, client/launchDebugger) must be + // answered so the server does not block. We never request debugging, so decline. + if (id.HasValue) + { + var response = new Dictionary + { + ["jsonrpc"] = "2.0", + ["id"] = id.Value, + ["result"] = new Dictionary { ["success"] = false }, + }; + try + { + WriteMessage(response); + } + catch (Exception ex) + { + EqtTrace.Warning("MtpServerConnection.HandleServerMessage: failed to answer '{0}': {1}", method, ex); + } + } + + break; + } + } + + private static async Task ReadHeadersAsync(Stream stream, CancellationToken cancellationToken) + { + int contentLength = -1; + while (true) + { + string? line = await ReadAsciiLineAsync(stream, cancellationToken).ConfigureAwait(false); + if (line is null) + { + return -1; // stream closed + } + + if (line.Length == 0) + { + return contentLength; // blank line terminates headers + } + + if (line.StartsWith(MtpConstants.ContentLengthHeader, StringComparison.OrdinalIgnoreCase)) + { + _ = int.TryParse(line.Substring(MtpConstants.ContentLengthHeader.Length).Trim(), out contentLength); + } + } + } + + private static async Task ReadAsciiLineAsync(Stream stream, CancellationToken cancellationToken) + { + var bytes = new List(64); + var one = new byte[1]; + while (true) + { + int read = await stream.ReadAsync(one, 0, 1, cancellationToken).ConfigureAwait(false); + if (read == 0) + { + return bytes.Count == 0 ? null : Encoding.ASCII.GetString(bytes.ToArray()); + } + + if (one[0] == (byte)'\n') + { + if (bytes.Count > 0 && bytes[bytes.Count - 1] == (byte)'\r') + { + bytes.RemoveAt(bytes.Count - 1); + } + + return Encoding.ASCII.GetString(bytes.ToArray()); + } + + bytes.Add(one[0]); + } + } + + private static async Task ReadExactlyAsync(Stream stream, int count, CancellationToken cancellationToken) + { + var buffer = new byte[count]; + int offset = 0; + while (offset < count) + { + int read = await stream.ReadAsync(buffer, offset, count - offset, cancellationToken).ConfigureAwait(false); + if (read == 0) + { + Array.Resize(ref buffer, offset); + break; + } + + offset += read; + } + + return buffer; + } + + private void FailPending(Exception exception) + { + foreach (var kvp in _pending) + { + kvp.Value.TrySetException(exception); + } + + _pending.Clear(); + } + + private static (string fileName, string arguments, string workingDirectory) BuildLaunch(string source, int port) + { + string serverArgs = $"{MtpConstants.ServerArgument} {MtpConstants.ClientPortArgument} {port} {MtpConstants.NoBannerArgument}"; + string workingDirectory = Path.GetDirectoryName(source) ?? Directory.GetCurrentDirectory(); + string extension = Path.GetExtension(source); + + if (extension.Equals(".exe", StringComparison.OrdinalIgnoreCase)) + { + return (source, serverArgs, workingDirectory); + } + + // A .NET MTP app is typically shipped as a dll with a sibling apphost .exe. Prefer the apphost + // if present, otherwise fall back to `dotnet `. + string apphost = Path.ChangeExtension(source, ".exe"); + return File.Exists(apphost) + ? (apphost, serverArgs, workingDirectory) + : ("dotnet", $"\"{source}\" {serverArgs}", workingDirectory); + } + + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + + try + { + _cts.Cancel(); + } + catch + { + // ignore + } + + try + { + _stream?.Dispose(); + } + catch + { + // ignore + } + + try + { + _client?.Dispose(); + } + catch + { + // ignore + } + + try + { + _listener.Stop(); + } + catch + { + // ignore + } + + try + { + if (_process is { HasExited: false }) + { +#if NETCOREAPP + _process.Kill(entireProcessTree: true); +#else + _process.Kill(); +#endif + } + } + catch + { + // ignore + } + + try + { + _process?.Dispose(); + } + catch + { + // ignore + } + + _cts.Dispose(); + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpTestNodeConverter.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpTestNodeConverter.cs new file mode 100644 index 0000000000..cf7e3fe67e --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpTestNodeConverter.cs @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; + +using Jsonite; + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.MTP; + +/// +/// Converts Microsoft.Testing.Platform (MTP) test nodes into vstest ObjectModel +/// and instances. +/// +/// The converter works purely off the MTP node shape (uid, display-name, +/// execution-state, time.duration-ms, error.*, location.*, traits) +/// so that an MTP application with no dependency on vstest at all still converts correctly. When the +/// optional vstest.* bridge properties are present they are used purely as enrichment. +/// +internal static class MtpTestNodeConverter +{ + internal static readonly TestProperty MtpUidProperty = TestProperty.Register( + MtpConstants.MtpUidPropertyId, + "MTP Uid", + typeof(string), + typeof(TestCase)); + + /// + /// Returns true when the node represents a runnable test (a leaf "action" node) rather than a + /// grouping node (namespace/class/suite). + /// + public static bool IsActionNode(JsonObject node) + => MtpJson.GetString(node, MtpConstants.NodeType) is "action"; + + public static string? GetExecutionState(JsonObject node) + => MtpJson.GetString(node, MtpConstants.ExecutionState); + + public static TestCase ToTestCase(JsonObject node, string source) + { + string uid = MtpJson.GetString(node, MtpConstants.Uid) ?? Guid.NewGuid().ToString(); + string fullyQualifiedName = MtpJson.GetString(node, MtpConstants.VsTestFullyQualifiedName) ?? uid; + string executorUri = MtpJson.GetString(node, MtpConstants.VsTestExecutorUri) ?? MtpConstants.DefaultExecutorUri; + + var testCase = new TestCase(fullyQualifiedName, new Uri(executorUri), source) + { + DisplayName = MtpJson.GetString(node, MtpConstants.DisplayName) ?? fullyQualifiedName, + }; + + testCase.SetPropertyValue(MtpUidProperty, uid); + + string? file = MtpJson.GetString(node, MtpConstants.LocationFile); + if (!string.IsNullOrEmpty(file)) + { + testCase.CodeFilePath = file; + if (MtpJson.TryGetInt(node, MtpConstants.LocationLineStart, out int line)) + { + testCase.LineNumber = line; + } + } + + AddTraits(node, testCase); + return testCase; + } + + public static TestResult ToTestResult(JsonObject node, string source) + { + var testCase = ToTestCase(node, source); + string? state = GetExecutionState(node); + + var result = new TestResult(testCase) + { + Outcome = ToOutcome(state), + DisplayName = testCase.DisplayName, + ErrorMessage = MtpJson.GetString(node, MtpConstants.ErrorMessage), + ErrorStackTrace = MtpJson.GetString(node, MtpConstants.ErrorStackTrace), + }; + + if (MtpJson.TryGetDouble(node, MtpConstants.TimeDurationMs, out double durationMs)) + { + result.Duration = TimeSpan.FromMilliseconds(durationMs); + } + + return result; + } + + public static bool IsTerminalState(string? state) + => state is MtpConstants.StatePassed + or MtpConstants.StateFailed + or MtpConstants.StateSkipped + or MtpConstants.StateError + or MtpConstants.StateTimedOut; + + private static TestOutcome ToOutcome(string? state) + => state switch + { + MtpConstants.StatePassed => TestOutcome.Passed, + MtpConstants.StateFailed => TestOutcome.Failed, + MtpConstants.StateError => TestOutcome.Failed, + MtpConstants.StateTimedOut => TestOutcome.Failed, + MtpConstants.StateSkipped => TestOutcome.Skipped, + _ => TestOutcome.None, + }; + + private static void AddTraits(JsonObject node, TestCase testCase) + { + if (MtpJson.GetValue(node, MtpConstants.Traits) is not JsonArray traits) + { + return; + } + + foreach (object? traitObject in traits) + { + if (traitObject is not JsonObject trait) + { + continue; + } + + foreach (KeyValuePair property in trait) + { + string value = property.Value as string ?? string.Empty; + testCase.Traits.Add(new Trait(property.Key, value)); + } + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Microsoft.TestPlatform.CrossPlatEngine.csproj b/src/Microsoft.TestPlatform.CrossPlatEngine/Microsoft.TestPlatform.CrossPlatEngine.csproj index c643cab47b..1242e70814 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Microsoft.TestPlatform.CrossPlatEngine.csproj +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Microsoft.TestPlatform.CrossPlatEngine.csproj @@ -3,7 +3,7 @@ Microsoft.TestPlatform.CrossPlatEngine - $(NetFrameworkMinimum);$(ExtensionTargetFrameworks) + $(NetFrameworkMinimum);$(ExtensionTargetFrameworks);$(NetCoreAppMinimum) false diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/PublicAPI.Unshipped.txt index 7dc5c58110..f4c6911c52 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/PublicAPI.Unshipped.txt @@ -1 +1,7 @@ #nullable enable +Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.IProxyManagerFactory +Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.IProxyManagerFactory.CreateDiscoveryManager() -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyDiscoveryManager! +Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.IProxyManagerFactory.CreateExecutionManager(Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces.IProxyDataCollectionManager? dataCollectionManager) -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyExecutionManager! +Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.MTP.MtpProxyManagerFactory +static Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.MTP.MtpProxyManagerFactory.CreateDiscoveryManager() -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyDiscoveryManager! +static Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.MTP.MtpProxyManagerFactory.CreateExecutionManager(Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces.IProxyDataCollectionManager? dataCollectionManager) -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyExecutionManager! diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/TestEngine.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/TestEngine.cs index b141404f06..cccb42fc4d 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/TestEngine.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/TestEngine.cs @@ -15,6 +15,7 @@ using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.Parallel; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection; +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Utilities; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; @@ -122,6 +123,17 @@ public IProxyDiscoveryManager GetDiscoveryManager( { var sources = discoveryCriteria.Sources.ToList(); var hostManager = _testHostProviderManager.GetTestHostManagerByRunConfiguration(runtimeProviderInfo.RunSettings, sources); + + // A runtime provider may host the run over its own protocol (e.g. Microsoft.Testing.Platform's + // JSON-RPC) instead of launching a vstest testhost. Such a provider supplies its own proxy managers + // via IProxyManagerFactory, so ask the resolved provider for the discovery manager rather than + // building the standard vstest testhost proxy. + if (hostManager is IProxyManagerFactory proxyManagerFactory) + { + EqtTrace.Verbose("TestEngine.GetDiscoveryManager: provider '{0}' supplies its own discovery manager.", hostManager.GetType().Name); + return proxyManagerFactory.CreateDiscoveryManager(); + } + hostManager?.Initialize(TestSessionMessageLogger.Instance, runtimeProviderInfo.RunSettings!); ThrowExceptionIfTestHostManagerIsNull(hostManager, runtimeProviderInfo.RunSettings); @@ -268,6 +280,20 @@ internal IProxyExecutionManager CreateNonParallelExecutionManager(IRequestData r // ProxyExecutionManager(&POM) var sources = runtimeProviderInfo.SourceDetails.Select(r => r.Source!).ToList(); var hostManager = _testHostProviderManager.GetTestHostManagerByRunConfiguration(runtimeProviderInfo.RunSettings, sources); + + // A runtime provider may host the run over its own protocol (e.g. Microsoft.Testing.Platform's + // JSON-RPC) instead of launching a vstest testhost. Such a provider supplies its own proxy managers + // via IProxyManagerFactory, so ask the resolved provider for the execution manager (wiring in data + // collection when enabled) rather than building the standard vstest testhost proxy. + if (hostManager is IProxyManagerFactory proxyManagerFactory) + { + IProxyDataCollectionManager? dataCollectionManager = isDataCollectorEnabled + ? new ProxyDataCollectionManager(requestData, runtimeProviderInfo.RunSettings, sources) + : null; + EqtTrace.Verbose("TestEngine.CreateNonParallelExecutionManager: provider '{0}' supplies its own execution manager (data collection: {1}).", hostManager.GetType().Name, isDataCollectorEnabled); + return proxyManagerFactory.CreateExecutionManager(dataCollectionManager); + } + ThrowExceptionIfTestHostManagerIsNull(hostManager, runtimeProviderInfo.RunSettings); hostManager!.Initialize(TestSessionMessageLogger.Instance, runtimeProviderInfo.RunSettings!); @@ -446,16 +472,20 @@ private List GetTestRuntimeProvidersForUniqueConfigurat out ITestRuntimeProvider? mostRecentlyCreatedInstance) { // Group source details to get unique frameworks and architectures for which we will run, so we can figure - // out which runtime providers would run them, and if the runtime provider is shared or not. + // out which runtime providers would run them, and if the runtime provider is shared or not. A source-aware + // provider (e.g. Microsoft.Testing.Platform) claims sources by their shape, not just their framework, so we + // include the claiming provider's type in the grouping key: this keeps such sources in their own + // configuration instead of being merged with generic (framework-only) sources of the same TFM/architecture. mostRecentlyCreatedInstance = null; var testRuntimeProviders = new List(); - var uniqueRunConfigurations = sourceToSourceDetailMap.Values.GroupBy(k => $"{k.Framework}|{k.Architecture}"); + var uniqueRunConfigurations = sourceToSourceDetailMap.Values.GroupBy(k => $"{k.Framework}|{k.Architecture}|{_testHostProviderManager.GetSourceAwareRuntimeProviderType(runSettings, k.Source!)?.AssemblyQualifiedName ?? string.Empty}"); foreach (var runConfiguration in uniqueRunConfigurations) { // It is okay to take the first (or any) source detail in the group. We are grouping to get the same source detail, so all architectures and frameworks are the same. var sourceDetail = runConfiguration.First(); var runsettingsXml = SourceDetailHelper.UpdateRunSettingsFromSourceDetail(runSettings, sourceDetail); var sources = runConfiguration.Select(c => c.Source!).ToList(); + var testRuntimeProvider = _testHostProviderManager.GetTestHostManagerByRunConfiguration(runsettingsXml, sources); if (testRuntimeProvider != null) @@ -604,6 +634,15 @@ private bool ShouldRunInProcess( return false; } + // A provider that supplies its own proxy managers (IProxyManagerFactory) hosts the run over its own + // protocol out-of-process (e.g. Microsoft.Testing.Platform's JSON-RPC), so it can never run in-process + // inside vstest.console. + if (testHostProviders.Any(p => p.Type is not null && typeof(IProxyManagerFactory).IsAssignableFrom(p.Type))) + { + EqtTrace.Info("TestEngine.ShouldRunInNoIsolation: This run contains a provider that hosts its own protocol out-of-process, running in isolation."); + return false; + } + var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(runsettings); if (runConfiguration.InIsolation) diff --git a/src/Microsoft.TestPlatform.ObjectModel/Friends.cs b/src/Microsoft.TestPlatform.ObjectModel/Friends.cs index 4e58caefe6..364f9a8ee5 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Friends.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Friends.cs @@ -9,6 +9,8 @@ [assembly: InternalsVisibleTo("datacollector, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("datacollector.arm64, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.VisualStudio.TestPlatform.Common, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +// The Microsoft.Testing.Platform runtime provider implements the internal ISourceAwareTestRuntimeProvider. +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.TestHostRuntimeProvider, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("vstest.console, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("vstest.console.arm64, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.VisualStudio.TestPlatform.Client, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] @@ -18,3 +20,4 @@ [assembly: InternalsVisibleTo("Microsoft.TestPlatform.Extensions.EventLogCollector.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("Microsoft.TestPlatform.ObjectModel.ManagedNameUtilities.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] [assembly: InternalsVisibleTo("vstest.console.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +[assembly: InternalsVisibleTo("Microsoft.TestPlatform.Common.UnitTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] diff --git a/src/Microsoft.TestPlatform.ObjectModel/Host/ISourceAwareTestRuntimeProvider.cs b/src/Microsoft.TestPlatform.ObjectModel/Host/ISourceAwareTestRuntimeProvider.cs new file mode 100644 index 0000000000..5f3286fefb --- /dev/null +++ b/src/Microsoft.TestPlatform.ObjectModel/Host/ISourceAwareTestRuntimeProvider.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Host; + +/// +/// Internal extension of that lets a runtime provider decide whether it +/// can host a run based on the actual test sources, not just the runsettings XML. +/// +/// +/// The base hook is +/// source-blind: it only receives the runsettings XML, so a provider cannot inspect the source assembly to +/// decide whether it owns it (for example, sniffing the Microsoft.Testing.Platform marker of an MTP app). +/// +/// A provider that implements this interface is consulted first by +/// TestRuntimeProviderManager.GetTestHostManagerByRunConfiguration, before the source-blind providers. +/// This lets a more specific provider claim a source by its shape and be selected ahead of the generic +/// providers that match only by target framework — without any global ordering/priority scheme and without +/// requiring the other providers to decline. Providers that do not implement this interface keep their +/// existing source-blind behavior unchanged. +/// +/// +/// This is intentionally an interface (detected via a type check) so it adds no +/// public API surface; in-box providers reach it through InternalsVisibleTo. +/// +/// +internal interface ISourceAwareTestRuntimeProvider : ITestRuntimeProvider +{ + /// + /// Determines whether this provider can host the given run for the specified test sources. + /// + /// The run configuration (runsettings XML). + /// The test sources (assembly/executable paths) that will be run. + /// + /// if this provider should host the given sources; otherwise . + /// + bool CanExecuteCurrentRunConfiguration(string? runsettingsXml, IEnumerable sources); +} diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DefaultTestHostManager.cs b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DefaultTestHostManager.cs index 0452489908..0ada6377f0 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DefaultTestHostManager.cs +++ b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DefaultTestHostManager.cs @@ -16,7 +16,7 @@ using Microsoft.TestPlatform.TestHostProvider; using Microsoft.TestPlatform.TestHostProvider.Hosting; -using Microsoft.TestPlatform.TestHostProvider.Resources; +using TestHostResources = Microsoft.TestPlatform.TestHostProvider.Resources.Resources; using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Extensions; using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers; @@ -210,7 +210,7 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo( // longer supported. Fail with a clear message instead of launching Mono. if (!_environment.OperatingSystem.Equals(PlatformOperatingSystem.Windows)) { - throw new TestPlatformException(Resources.NetFrameworkTestsNotSupportedOnNonWindows); + throw new TestPlatformException(TestHostResources.NetFrameworkTestsNotSupportedOnNonWindows); } var launcherPath = testhostProcessPath; @@ -478,7 +478,7 @@ private IEnumerable FilterExtensionsBasedOnVersion(IEnumerable e if (conflictingExtensions.Count != 0) { var extensionsString = string.Join("\n", conflictingExtensions.Select(kv => $" {kv.Key} : {kv.Value}")); - string message = string.Format(CultureInfo.CurrentCulture, Resources.MultipleFileVersions, extensionsString); + string message = string.Format(CultureInfo.CurrentCulture, TestHostResources.MultipleFileVersions, extensionsString); _messageLogger.SendMessage(TestMessageLevel.Warning, message); } diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs index 5dd0a846f1..8cc3471d56 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs +++ b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs @@ -19,7 +19,7 @@ #endif using Microsoft.TestPlatform.TestHostProvider; using Microsoft.TestPlatform.TestHostProvider.Hosting; -using Microsoft.TestPlatform.TestHostProvider.Resources; +using TestHostResources = Microsoft.TestPlatform.TestHostProvider.Resources.Resources; using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Extensions; using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Helpers; @@ -425,7 +425,7 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo( // built-in testhost (it would just discover no tests) - throw and point the user at Microsoft.NET.Test.Sdk. if (!IsNativeModule(sourcePath)) { - string message = string.Format(CultureInfo.CurrentCulture, Resources.CouldNotFindTesthost, sourcePath, sourceDirectory); + string message = string.Format(CultureInfo.CurrentCulture, TestHostResources.CouldNotFindTesthost, sourcePath, sourceDirectory); throw new TestPlatformException(message); } @@ -505,7 +505,7 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo( if (testHostPath.IsNullOrEmpty()) { - string message = string.Format(CultureInfo.CurrentCulture, Resources.CouldNotFindTesthost, sourcePath, sourceDirectory); + string message = string.Format(CultureInfo.CurrentCulture, TestHostResources.CouldNotFindTesthost, sourcePath, sourceDirectory); throw new TestPlatformException(message); } @@ -545,7 +545,7 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo( PlatformArchitecture finalTargetArchitecture = forceToX64 ? PlatformArchitecture.X64 : targetArchitecture; if (!_dotnetHostHelper.TryGetDotnetPathByArchitecture(finalTargetArchitecture, muxerResolutionStrategy, out string? muxerPath)) { - string message = string.Format(CultureInfo.CurrentCulture, Resources.NoDotnetMuxerFoundForArchitecture, $"dotnet{(_platformEnvironment.OperatingSystem == PlatformOperatingSystem.Windows ? ".exe" : string.Empty)}", finalTargetArchitecture.ToString()); + string message = string.Format(CultureInfo.CurrentCulture, TestHostResources.NoDotnetMuxerFoundForArchitecture, $"dotnet{(_platformEnvironment.OperatingSystem == PlatformOperatingSystem.Windows ? ".exe" : string.Empty)}", finalTargetArchitecture.ToString()); EqtTrace.Error(message); throw new TestPlatformException(message); } diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/MtpTestRuntimeProvider.cs b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/MtpTestRuntimeProvider.cs new file mode 100644 index 0000000000..f80dcc7817 --- /dev/null +++ b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/MtpTestRuntimeProvider.cs @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers; +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.MTP; +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Host; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting; + +/// +/// Runtime provider for Microsoft.Testing.Platform (MTP) test applications. +/// +/// +/// An MTP test application is its own test host: it is launched in server mode and driven directly over the +/// Microsoft.Testing.Platform JSON-RPC protocol, not the vstest testhost protocol. This provider therefore does +/// not launch a vstest testhost at all — the launch-related members of throw +/// . Instead it: +/// +/// +/// claims MTP sources via (source-aware detection using the +/// build-time Microsoft.Testing.Platform marker), so it is selected ahead of the generic testhost providers +/// that match only by target framework; and +/// +/// +/// supplies its own discovery/execution proxy managers via , so the +/// engine drives MTP sources over the MTP proxies without any protocol-specific branching in the engine. +/// +/// +/// This is why the provider is registered like a normal runtime provider yet routes to +/// / instead of a vstest testhost. +/// +[ExtensionUri(MicrosoftTestingPlatformHostUri)] +[FriendlyName(MicrosoftTestingPlatformHostFriendlyName)] +public class MtpTestRuntimeProvider : ISourceAwareTestRuntimeProvider, IProxyManagerFactory +{ + private const string MicrosoftTestingPlatformHostUri = "HostProvider://MicrosoftTestingPlatformHost"; + private const string MicrosoftTestingPlatformHostFriendlyName = "MicrosoftTestingPlatformHost"; + + // The launch-related members are not supported because an MTP application hosts itself; it is never + // launched as a vstest testhost. Discovery and execution go through the MTP proxy managers instead. + private const string NotSupportedMessage = "Microsoft.Testing.Platform applications are hosted over the MTP protocol and are not launched as a vstest test host."; + + event EventHandler? ITestRuntimeProvider.HostLaunched + { + add { } + remove { } + } + + event EventHandler? ITestRuntimeProvider.HostExited + { + add { } + remove { } + } + + bool ITestRuntimeProvider.Shared => false; + + void ITestRuntimeProvider.Initialize(IMessageLogger? logger, string runsettingsXml) + { + } + + // Source-blind resolution can never own a run: without the sources we cannot tell whether they are MTP + // applications, so we decline and let the source-aware path below make the decision. + bool ITestRuntimeProvider.CanExecuteCurrentRunConfiguration(string? runsettingsXml) => false; + + // Source-aware resolution: this provider owns the run only when every source is a Microsoft.Testing.Platform + // application. A mixed set (some MTP, some classic) is split into separate configurations upstream, so each + // group asked here is homogeneous. + bool ISourceAwareTestRuntimeProvider.CanExecuteCurrentRunConfiguration(string? runsettingsXml, IEnumerable sources) + => AllSourcesAreMicrosoftTestingPlatform(sources); + + void ITestRuntimeProvider.SetCustomLauncher(ITestHostLauncher customLauncher) + { + } + + TestHostConnectionInfo ITestRuntimeProvider.GetTestHostConnectionInfo() => throw new NotSupportedException(NotSupportedMessage); + + Task ITestRuntimeProvider.LaunchTestHostAsync(TestProcessStartInfo testHostStartInfo, CancellationToken cancellationToken) => throw new NotSupportedException(NotSupportedMessage); + + TestProcessStartInfo ITestRuntimeProvider.GetTestHostProcessStartInfo(IEnumerable sources, IDictionary? environmentVariables, TestRunnerConnectionInfo connectionInfo) => throw new NotSupportedException(NotSupportedMessage); + + IEnumerable ITestRuntimeProvider.GetTestPlatformExtensions(IEnumerable sources, IEnumerable extensions) => Enumerable.Empty(); + + IEnumerable ITestRuntimeProvider.GetTestSources(IEnumerable sources) => sources; + + Task ITestRuntimeProvider.CleanTestHostAsync(CancellationToken cancellationToken) => Task.CompletedTask; + + IProxyDiscoveryManager IProxyManagerFactory.CreateDiscoveryManager() => MtpProxyManagerFactory.CreateDiscoveryManager(); + + IProxyExecutionManager IProxyManagerFactory.CreateExecutionManager(IProxyDataCollectionManager? dataCollectionManager) + => MtpProxyManagerFactory.CreateExecutionManager(dataCollectionManager); + + private static bool AllSourcesAreMicrosoftTestingPlatform(IEnumerable sources) + { + var any = false; + foreach (var source in sources) + { + any = true; + + // The detector never throws: for a null/empty/unreadable source it returns false, which correctly + // disqualifies the group. + if (!MicrosoftTestingPlatformDetector.IsMicrosoftTestingPlatformApp(source)) + { + return false; + } + } + + return any; + } +} diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Microsoft.TestPlatform.TestHostProvider.csproj b/src/Microsoft.TestPlatform.TestHostProvider/Microsoft.TestPlatform.TestHostProvider.csproj index 2516fbbbb9..75453fd5fc 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Microsoft.TestPlatform.TestHostProvider.csproj +++ b/src/Microsoft.TestPlatform.TestHostProvider/Microsoft.TestPlatform.TestHostProvider.csproj @@ -25,6 +25,12 @@ true + + diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Properties/AssemblyInfo.cs b/src/Microsoft.TestPlatform.TestHostProvider/Properties/AssemblyInfo.cs index 9ee28e58ff..c27ef31832 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Properties/AssemblyInfo.cs +++ b/src/Microsoft.TestPlatform.TestHostProvider/Properties/AssemblyInfo.cs @@ -4,4 +4,4 @@ using Microsoft.VisualStudio.TestPlatform; using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting; -[assembly: TestExtensionTypes(typeof(DefaultTestHostManager), typeof(DotnetTestHostManager))] +[assembly: TestExtensionTypes(typeof(DefaultTestHostManager), typeof(DotnetTestHostManager), typeof(MtpTestRuntimeProvider))] diff --git a/src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/PublicAPI.Unshipped.txt index 7dc5c58110..de7b9d8e41 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/Microsoft.TestPlatform.TestHostProvider/PublicAPI/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting.MtpTestRuntimeProvider +Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting.MtpTestRuntimeProvider.MtpTestRuntimeProvider() -> void diff --git a/src/vstest.console/CommandLine/AssemblyMetadataProvider.cs b/src/vstest.console/CommandLine/AssemblyMetadataProvider.cs index 8f18e162b7..419884c903 100644 --- a/src/vstest.console/CommandLine/AssemblyMetadataProvider.cs +++ b/src/vstest.console/CommandLine/AssemblyMetadataProvider.cs @@ -11,6 +11,7 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers.Interfaces; @@ -92,6 +93,23 @@ public Architecture GetArchitecture(string assemblyPath) return archType; } + /// + public bool IsMicrosoftTestingPlatformApp(string filePath) + { + try + { + using var assemblyStream = _fileHelper.GetStream(filePath, FileMode.Open, FileAccess.Read); + var result = MicrosoftTestingPlatformDetector.IsMicrosoftTestingPlatformApp(assemblyStream); + EqtTrace.Info("AssemblyMetadataProvider.IsMicrosoftTestingPlatformApp: '{0}' for source: '{1}'", result, filePath); + return result; + } + catch (Exception ex) + { + EqtTrace.Warning("AssemblyMetadataProvider.IsMicrosoftTestingPlatformApp: failed to read assembly metadata, exception: {0} for assembly: {1}", ex, filePath); + return false; + } + } + private Architecture GetArchitectureFromAssemblyMetadata(string path) { Architecture arch = Architecture.AnyCPU; diff --git a/src/vstest.console/CommandLine/Interfaces/IAssemblyMetadataProvider.cs b/src/vstest.console/CommandLine/Interfaces/IAssemblyMetadataProvider.cs index 5d4e83f2c6..500d934618 100644 --- a/src/vstest.console/CommandLine/Interfaces/IAssemblyMetadataProvider.cs +++ b/src/vstest.console/CommandLine/Interfaces/IAssemblyMetadataProvider.cs @@ -21,4 +21,11 @@ internal interface IAssemblyMetadataProvider /// Determines Architecture from filePath. /// Architecture GetArchitecture(string filePath); + + /// + /// Determines whether the assembly at is a Microsoft.Testing.Platform (MTP) + /// application, i.e. it is marked with + /// [assembly: AssemblyMetadata("Microsoft.Testing.Platform.Application", "true")]. + /// + bool IsMicrosoftTestingPlatformApp(string filePath); } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiscoveryTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiscoveryTests.cs index 81f4687965..b3aa31a0da 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiscoveryTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/DiscoveryTests.cs @@ -98,7 +98,7 @@ public void TypesToLoadAttributeTests() {"Microsoft.TestPlatform.Extensions.BlameDataCollector.dll", ["Microsoft.TestPlatform.Extensions.BlameDataCollector.BlameLogger", "Microsoft.TestPlatform.Extensions.BlameDataCollector.BlameCollector"] }, {"Microsoft.VisualStudio.TestPlatform.Extensions.Html.TestLogger.dll", ["Microsoft.VisualStudio.TestPlatform.Extensions.HtmlLogger.HtmlLogger"] }, {"Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger.dll", ["Microsoft.VisualStudio.TestPlatform.Extensions.TrxLogger.TrxLogger"] }, - {"Microsoft.TestPlatform.TestHostRuntimeProvider.dll", ["Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting.DefaultTestHostManager", "Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting.DotnetTestHostManager"] + {"Microsoft.TestPlatform.TestHostRuntimeProvider.dll", ["Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting.DefaultTestHostManager", "Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting.DotnetTestHostManager", "Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Hosting.MtpTestRuntimeProvider"] } }; diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MtpUnderVstestTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MtpUnderVstestTests.cs new file mode 100644 index 0000000000..349bccb7ef --- /dev/null +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MtpUnderVstestTests.cs @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; + +using Microsoft.TestPlatform.TestUtilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.TestPlatform.AcceptanceTests; + +/// +/// End-to-end coverage for running a Microsoft.Testing.Platform (MTP) test application under +/// vstest.console. vstest detects the MTP app from its assembly metadata +/// ([assembly: AssemblyMetadata("Microsoft.Testing.Platform.Application", "true")]) and drives it +/// over the MTP JSON-RPC protocol via the MtpTestRuntimeProvider, translating MTP test-node updates back +/// into vstest results so the console summary and loggers keep working. +/// +[TestClass] +public class MtpUnderVstestTests : AcceptanceTestBase +{ + // MtpMSTestProject is an MSTest project built as an MTP application (EnableMSTestRunner): two tests + // pass, one fails, one is skipped. + private const string MtpApp = "MtpMSTestProject.dll"; + + // MSTestProject1 is a classic vstest MSTest project driven by the vstest testhost: one passes, one + // fails, one is skipped. + private const string ClassicApp = "MSTestProject1.dll"; + + [TestMethod] + // MTP apps are .NET (Core) applications. Pin the testhost axis to .NET so we drive the net11.0 MTP app + // from both the .NET Framework and the .NET console (the .NET Framework console exercises the Jsonite + // JSON-RPC path; it is Windows-only and skipped elsewhere by the matrix). + [TestMatrix(testHost: Target.Net)] + public void RunMtpApplicationExecutesTestsOverMtpProtocol(RunnerInfo runnerInfo) + { + SetTestEnvironment(_testEnvironment, runnerInfo); + + var arguments = PrepareArguments( + GetAssetFullPath(MtpApp), + testAdapterPath: null, + runSettings: string.Empty, + FrameworkArgValue, + runnerInfo.InIsolationValue, + resultsDirectory: TempDirectory.Path); + + InvokeVsTest(arguments); + + ValidateSummaryStatus(2, 1, 1); + } + + [TestMethod] + // A single vstest.console invocation over a classic vstest project AND an MTP application in one run: + // the classic source is driven by the vstest testhost, the MTP source over the MTP protocol. vstest + // groups the two sources into separate hosts and aggregates their results. + [TestMatrix(testHost: Target.Net)] + public void RunMixedClassicAndMtpApplicationsInSingleRun(RunnerInfo runnerInfo) + { + SetTestEnvironment(_testEnvironment, runnerInfo); + + var arguments = PrepareArguments( + [GetAssetFullPath(ClassicApp), GetAssetFullPath(MtpApp)], + testAdapterPath: null, + runSettings: string.Empty, + FrameworkArgValue, + runnerInfo.InIsolationValue, + resultsDirectory: TempDirectory.Path); + + InvokeVsTest(arguments); + + // Classic 1/1/1 + MTP 2/1/1 aggregated into one run summary. + ValidateSummaryStatus(3, 2, 2); + } + + [TestMethod] + // Prove a TRX logger aggregates results from both the classic and the MTP source in a mixed run into a + // single .trx with all seven tests. + [TestMatrix(testHost: Target.Net)] + public void RunMixedClassicAndMtpApplicationsWritesSingleTrx(RunnerInfo runnerInfo) + { + SetTestEnvironment(_testEnvironment, runnerInfo); + + var trxFileName = "mixed.trx"; + var arguments = PrepareArguments( + [GetAssetFullPath(ClassicApp), GetAssetFullPath(MtpApp)], + testAdapterPath: null, + runSettings: string.Empty, + FrameworkArgValue, + runnerInfo.InIsolationValue, + resultsDirectory: TempDirectory.Path); + arguments = string.Concat(arguments, $" /logger:trx;LogFileName={trxFileName}"); + + InvokeVsTest(arguments); + + ValidateSummaryStatus(3, 2, 2); + + var trxPath = Path.Combine(TempDirectory.Path, trxFileName); + Assert.IsTrue(File.Exists(trxPath), "Expected a single TRX to be written for the mixed run at '{0}'.", trxPath); + } +} diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/Hosting/TestHostProviderManagerTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/Hosting/TestHostProviderManagerTests.cs index 18b43def79..a988aad829 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/Hosting/TestHostProviderManagerTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/Hosting/TestHostProviderManagerTests.cs @@ -164,6 +164,63 @@ public void TestHostProviderManagerShouldReturnNullIfTargetFrameworkIsInvalidFra Assert.IsNull(manager.GetTestHostManagerByRunConfiguration(runSettingsXml, null)); } + [TestMethod] + public void GetTestHostManagerByRunConfigurationShouldPreferSourceAwareProviderWhenSourcesMatch() + { + // A .NET Framework runsettings would normally resolve to the source-blind CustomTestHost, but the + // source-aware provider claims the source in the first pass and must win. + string runSettingsXml = @" + + + 0 + x64 + .NETFramework,Version=v4.5.1 + + "; + + var manager = TestRuntimeProviderManager.Instance; + var testHostManager = manager.GetTestHostManagerByRunConfiguration(runSettingsXml, new List { @"C:\temp\app.mtpapp.dll" }); + + Assert.AreEqual(typeof(SourceAwareTestHost), testHostManager!.GetType()); + } + + [TestMethod] + public void GetTestHostManagerByRunConfigurationShouldFallBackToLegacyWhenSourceAwareDeclines() + { + // The source does not match the source-aware provider, so selection must fall through to the + // source-blind resolution and pick the .NET Core host manager based on the framework. + string runSettingsXml = string.Concat( + @" +0 x64 ", + ".NETCoreApp,Version=v1.0", + " "); + + var manager = TestRuntimeProviderManager.Instance; + var testHostManager = manager.GetTestHostManagerByRunConfiguration(runSettingsXml, new List { @"C:\temp\normal.dll" }); + + Assert.AreEqual(typeof(TestableTestHostManager), testHostManager!.GetType()); + } + + [TestMethod] + public void GetTestHostManagerByRunConfigurationShouldIgnoreSourceAwareProviderWhenSourcesAreNull() + { + // Passing null sources must skip the source-aware pass entirely (back-compat), resolving purely by + // run configuration. + string runSettingsXml = @" + + + 0 + x64 + .NETFramework,Version=v4.5.1 + + "; + + var manager = TestRuntimeProviderManager.Instance; + var testHostManager = manager.GetTestHostManagerByRunConfiguration(runSettingsXml, null); + + Assert.AreEqual(typeof(CustomTestHost), testHostManager!.GetType()); + } + #region Implementations [ExtensionUri("executor://DesktopTestHost")] @@ -312,5 +369,84 @@ public IEnumerable GetTestSources(IEnumerable sources) } } + [ExtensionUri("executor://SourceAwareTestHost")] + [FriendlyName("SourceAwareTestHost")] + private class SourceAwareTestHost : ITestRuntimeProvider, ISourceAwareTestRuntimeProvider + { + public event EventHandler? HostLaunched; + + public event EventHandler? HostExited; + + public bool Shared => false; + + // Deliberately "greedy" source-blind answer: this would match any runsettings, yet the manager must + // never select a source-aware provider through the source-blind pass. It may only be chosen via the + // source-aware hook below. This proves the exclusion is enforced by the manager, not by the provider + // politely declining. + public bool CanExecuteCurrentRunConfiguration(string? runsettingsXml) => true; + + public bool CanExecuteCurrentRunConfiguration(string? runsettingsXml, IEnumerable sources) + { + foreach (var source in sources) + { + if (source.EndsWith(".mtpapp.dll", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + + public TestProcessStartInfo GetTestHostProcessStartInfo(IEnumerable sources, IDictionary? environmentVariables, TestRunnerConnectionInfo connectionInfo) + { + throw new NotImplementedException(); + } + + public IEnumerable GetTestPlatformExtensions(IEnumerable sources, IEnumerable extensions) + { + throw new NotImplementedException(); + } + + public void Initialize(IMessageLogger? logger, string runsettingsXml) + { + } + + public Task LaunchTestHostAsync(TestProcessStartInfo testHostStartInfo, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public void OnHostExited(HostProviderEventArgs _) + { + HostExited?.Invoke(this, new HostProviderEventArgs("Error")); + } + + public void OnHostLaunched(HostProviderEventArgs _) + { + HostLaunched?.Invoke(this, new HostProviderEventArgs("Error")); + } + + public void SetCustomLauncher(ITestHostLauncher customLauncher) + { + throw new NotImplementedException(); + } + + public Task CleanTestHostAsync(CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public TestHostConnectionInfo GetTestHostConnectionInfo() + { + throw new NotImplementedException(); + } + + public IEnumerable GetTestSources(IEnumerable sources) + { + return sources; + } + } + #endregion } diff --git a/test/TestAssets/MtpMSTestProject/MtpMSTestProject.csproj b/test/TestAssets/MtpMSTestProject/MtpMSTestProject.csproj new file mode 100644 index 0000000000..ac56343cab --- /dev/null +++ b/test/TestAssets/MtpMSTestProject/MtpMSTestProject.csproj @@ -0,0 +1,31 @@ + + + + + + net8.0;net11.0 + Exe + enable + + + true + true + + + + + $(MSTestTestFrameworkVersion) + + + $(MSTestTestAdapterVersion) + + + $(MicrosoftTestingPlatformVersion) + + + + diff --git a/test/TestAssets/MtpMSTestProject/UnitTests.cs b/test/TestAssets/MtpMSTestProject/UnitTests.cs new file mode 100644 index 0000000000..d6846e4a9f --- /dev/null +++ b/test/TestAssets/MtpMSTestProject/UnitTests.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace MtpMSTestProject; + +[TestClass] +public class UnitTests +{ + // Values are produced at runtime so the MSTest analyzers do not flag the asserts as + // always-true / always-false. + private static int Add(int a, int b) => a + b; + + [TestMethod] + public void TestPasses() + { + Assert.AreEqual(4, Add(2, 2)); + } + + [TestMethod] + public void TestPassesToo() + { + Assert.AreEqual(2, Add(1, 1)); + } + + [TestMethod] + public void TestFails() + { + Assert.Fail("intentional failure to validate outcome mapping"); + } + + [TestMethod] + [Ignore("intentionally skipped")] + public void TestSkipped() + { + Assert.Fail("should never run"); + } +} diff --git a/test/TestAssets/MtpPureProject/Calculator.cs b/test/TestAssets/MtpPureProject/Calculator.cs new file mode 100644 index 0000000000..8bf00ce99e --- /dev/null +++ b/test/TestAssets/MtpPureProject/Calculator.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace MtpPureProject; + +/// +/// Trivial code under test. The passing tests exercise and ; +/// is intentionally left uncovered so the coverage report shows a real +/// covered/uncovered split for this assembly. +/// +public static class Calculator +{ + public static int Add(int a, int b) + { + return a + b; + } + + public static int Multiply(int a, int b) + { + int result = 0; + for (int i = 0; i < b; i++) + { + result += a; + } + + return result; + } + + public static int Divide(int a, int b) + { + if (b == 0) + { + throw new System.DivideByZeroException(); + } + + return a / b; + } +} diff --git a/test/TestAssets/MtpPureProject/MtpPureProject.csproj b/test/TestAssets/MtpPureProject/MtpPureProject.csproj new file mode 100644 index 0000000000..4aec3a7ccc --- /dev/null +++ b/test/TestAssets/MtpPureProject/MtpPureProject.csproj @@ -0,0 +1,40 @@ + + + + + + net8.0 + Exe + enable + disable + + + true + + + portable + true + + + + + + + + + + + + + diff --git a/test/TestAssets/MtpPureProject/Program.cs b/test/TestAssets/MtpPureProject/Program.cs new file mode 100644 index 0000000000..ab03e4497a --- /dev/null +++ b/test/TestAssets/MtpPureProject/Program.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Threading.Tasks; + +using Microsoft.Testing.Platform.Builder; +using Microsoft.Testing.Platform.Capabilities.TestFramework; + +namespace MtpPureProject; + +public static class Program +{ + public static async Task Main(string[] args) + { + ITestApplicationBuilder builder = await TestApplication.CreateBuilderAsync(args); + + builder.RegisterTestFramework( + _ => new TestFrameworkCapabilities(), + (_, serviceProvider) => new PureTestFramework()); + + using ITestApplication app = await builder.BuildAsync(); + return await app.RunAsync(); + } +} diff --git a/test/TestAssets/MtpPureProject/PureTestFramework.cs b/test/TestAssets/MtpPureProject/PureTestFramework.cs new file mode 100644 index 0000000000..98a26fe60d --- /dev/null +++ b/test/TestAssets/MtpPureProject/PureTestFramework.cs @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +using Microsoft.Testing.Platform.Extensions.Messages; +using Microsoft.Testing.Platform.Extensions.TestFramework; +using Microsoft.Testing.Platform.Requests; +using Microsoft.Testing.Platform.TestHost; + +namespace MtpPureProject; + +/// +/// A hand-rolled Microsoft.Testing.Platform test framework. It has no knowledge of vstest, MSTest, +/// or Microsoft.TestPlatform.ObjectModel. It publishes test nodes over the MTP protocol directly, +/// which is exactly what vstest's MTP provider consumes. +/// +/// It exposes four tests, mirroring the MSTest asset so results are directly comparable: +/// - TestAddPasses : passes (exercises Calculator.Add) +/// - TestMultiplyPasses : passes (exercises Calculator.Multiply) +/// - TestFails : fails (throws) +/// - TestSkipped : skipped +/// Expected: Passed 2, Failed 1, Skipped 1, Total 4. +/// +internal sealed class PureTestFramework : ITestFramework, IDataProducer +{ + private static readonly SessionUid SessionUid = new("PureMtpSession"); + + private static readonly TestDefinition[] Tests = + [ + new("TestAddPasses", "TestAddPasses", static () => + { + if (Calculator.Add(2, 3) != 5) + { + throw new InvalidOperationException("Add returned the wrong value."); + } + }), + new("TestMultiplyPasses", "TestMultiplyPasses", static () => + { + if (Calculator.Multiply(4, 3) != 12) + { + throw new InvalidOperationException("Multiply returned the wrong value."); + } + }), + new("TestFails", "TestFails", static () => + throw new InvalidOperationException("This test fails on purpose.")), + new("TestSkipped", "TestSkipped", Body: null, Skip: true), + ]; + + public string Uid => nameof(PureTestFramework); + + public string Version => "1.0.0"; + + public string DisplayName => "Pure MTP Test Framework"; + + public string Description => "A minimal Microsoft.Testing.Platform test framework with no vstest dependency."; + + public Type[] DataTypesProduced => [typeof(TestNodeUpdateMessage)]; + + public Task IsEnabledAsync() => Task.FromResult(true); + + public Task CreateTestSessionAsync(CreateTestSessionContext context) + => Task.FromResult(new CreateTestSessionResult { IsSuccess = true }); + + public Task CloseTestSessionAsync(CloseTestSessionContext context) + => Task.FromResult(new CloseTestSessionResult { IsSuccess = true }); + + public async Task ExecuteRequestAsync(ExecuteRequestContext context) + { + switch (context.Request) + { + case DiscoverTestExecutionRequest: + foreach (TestDefinition test in Tests) + { + await PublishAsync(context, test.Uid, test.DisplayName, new DiscoveredTestNodeStateProperty()); + } + + break; + + case RunTestExecutionRequest: + foreach (TestDefinition test in Tests) + { + IProperty state = RunOne(test); + await PublishAsync(context, test.Uid, test.DisplayName, state); + } + + break; + } + + context.Complete(); + } + + private static IProperty RunOne(TestDefinition test) + { + if (test.Skip) + { + return new SkippedTestNodeStateProperty(); + } + + try + { + test.Body!(); + return new PassedTestNodeStateProperty(); + } + catch (Exception ex) + { + return new FailedTestNodeStateProperty(ex); + } + } + + private Task PublishAsync(ExecuteRequestContext context, string uid, string displayName, IProperty state) + => context.MessageBus.PublishAsync( + this, + new TestNodeUpdateMessage( + SessionUid, + new TestNode + { + Uid = uid, + DisplayName = displayName, + Properties = new PropertyBag(state), + })); + + private sealed record TestDefinition(string Uid, string DisplayName, Action? Body, bool Skip = false); +} diff --git a/test/TestAssets/TestAssets.slnx b/test/TestAssets/TestAssets.slnx index 749d11de58..b6b1787a02 100644 --- a/test/TestAssets/TestAssets.slnx +++ b/test/TestAssets/TestAssets.slnx @@ -7,6 +7,7 @@ + diff --git a/test/vstest.ProgrammerTests/Fakes/FakeAssemblyMetadataProvider.cs b/test/vstest.ProgrammerTests/Fakes/FakeAssemblyMetadataProvider.cs index cf1c4dfbb3..9b6dbf3ab5 100644 --- a/test/vstest.ProgrammerTests/Fakes/FakeAssemblyMetadataProvider.cs +++ b/test/vstest.ProgrammerTests/Fakes/FakeAssemblyMetadataProvider.cs @@ -31,4 +31,6 @@ public FrameworkName GetFrameworkName(string filePath) var file = FakeFileHelper.GetFakeFile(filePath); return file.FrameworkName; } + + public bool IsMicrosoftTestingPlatformApp(string filePath) => false; } diff --git a/test/vstest.ProgrammerTests/Fakes/FakeTestRuntimeProviderManager.cs b/test/vstest.ProgrammerTests/Fakes/FakeTestRuntimeProviderManager.cs index acf11b261f..117dcf6ec2 100644 --- a/test/vstest.ProgrammerTests/Fakes/FakeTestRuntimeProviderManager.cs +++ b/test/vstest.ProgrammerTests/Fakes/FakeTestRuntimeProviderManager.cs @@ -69,4 +69,6 @@ public ITestRuntimeProvider GetTestHostManagerByUri(string hostUri) { throw new NotImplementedException(); } + + public Type? GetSourceAwareRuntimeProviderType(string? runConfiguration, string source) => null; } From 56d779e90b2f894e3d053d634ed3befadb22108d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Tue, 7 Jul 2026 13:37:45 +0200 Subject: [PATCH 18/87] Inject TestSessionPool into the CrossPlatEngine session proxies (#16225) `TestSessionPool` is reached through the mutable public static `TestSessionPool.Instance` from every site that coordinates a test session: the writer `ProxyTestSessionManager.AddSession`, and the readers `TestEngine.TryTakeProxy`, `ProxyDiscoveryManager.ReturnProxy` and `ProxyExecutionManager.ReturnProxy`. Because it is process-wide state, the writer and readers of a given session only line up by virtue of all resolving the same static, which is the kind of implicit shared state that makes this code hard to test in isolation. Thread one `TestSessionPool?` from `TestEngine` (the single place that constructs the session-mode proxies) into `ProxyTestSessionManager`, `ProxyDiscoveryManager` and `ProxyExecutionManager`. Each holds the injected pool in a nullable field and reads `(_testSessionPool ?? TestSessionPool.Instance)` at the call site, so when nothing is injected the behavior is byte-for-byte what it was before. The new parameters live on internal constructors/overloads only, so no shipped public signature changes and `TestSessionPool.Instance` keeps working for the callers that still use it. `TestRequestManager.KillSession` in vstest.console is intentionally left on the static - it is not one of the objects `TestEngine` builds, so injecting there would be a separate seam. Adds a `ProxyTestSessionManagerTests` case that injects one pool as the shared instance and a different pool behind the static default: `StartSession` writes to the injected pool and a `TryTakeProxy` off the same injected instance observes the proxy, while the static default never sees the session. That guards the same-instance contract the refactor relies on. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Client/ProxyDiscoveryManager.cs | 7 ++- .../Client/ProxyExecutionManager.cs | 11 +++- .../TestEngine.cs | 17 +++--- .../TestSession/ProxyTestSessionManager.cs | 14 ++++- .../Client/ProxyTestSessionManagerTests.cs | 52 ++++++++++++++++++- 5 files changed, 88 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyDiscoveryManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyDiscoveryManager.cs index b0c527368a..bfb5dace93 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyDiscoveryManager.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyDiscoveryManager.cs @@ -29,6 +29,7 @@ public class ProxyDiscoveryManager : IProxyDiscoveryManager, IBaseProxy, ITestDi { private readonly TestSessionInfo? _testSessionInfo; private readonly Func? _proxyOperationManagerCreator; + private readonly TestSessionPool? _testSessionPool; private readonly DiscoveryDataAggregator _discoveryDataAggregator; private readonly IFileHelper _fileHelper; private readonly IDataSerializer _dataSerializer; @@ -56,11 +57,13 @@ public ProxyDiscoveryManager( internal ProxyDiscoveryManager( TestSessionInfo testSessionInfo, Func proxyOperationManagerCreator, - DiscoveryDataAggregator discoveryDataAggregator) + DiscoveryDataAggregator discoveryDataAggregator, + TestSessionPool? testSessionPool = null) { // Filling in test session info and proxy information. _testSessionInfo = testSessionInfo; _proxyOperationManagerCreator = proxyOperationManagerCreator; + _testSessionPool = testSessionPool; _discoveryDataAggregator = discoveryDataAggregator; _dataSerializer = JsonDataSerializer.Instance; _fileHelper = new FileHelper(); @@ -289,7 +292,7 @@ public void Close() return; } - TestSessionPool.Instance.ReturnProxy(_testSessionInfo, _proxyOperationManager.Id); + (_testSessionPool ?? TestSessionPool.Instance).ReturnProxy(_testSessionInfo, _proxyOperationManager.Id); } /// diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyExecutionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyExecutionManager.cs index 42442f0735..5939e47852 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyExecutionManager.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyExecutionManager.cs @@ -34,6 +34,7 @@ internal class ProxyExecutionManager : IProxyExecutionManager, IBaseProxy, IInte { private readonly TestSessionInfo? _testSessionInfo; private readonly Func? _proxyOperationManagerCreator; + private readonly TestSessionPool? _testSessionPool; private readonly IFileHelper _fileHelper; private readonly IDataSerializer _dataSerializer; private readonly bool _debugEnabledForTestSession; @@ -73,14 +74,20 @@ public CancellationTokenSource CancellationTokenSource /// /// A flag indicating if debugging should be enabled or not. /// + /// + /// The test session pool to return proxies to, or to use the shared + /// . + /// public ProxyExecutionManager( TestSessionInfo testSessionInfo, Func proxyOperationManagerCreator, - bool debugEnabledForTestSession) + bool debugEnabledForTestSession, + TestSessionPool? testSessionPool = null) { // Filling in test session info and proxy information. _testSessionInfo = testSessionInfo; _proxyOperationManagerCreator = proxyOperationManagerCreator; + _testSessionPool = testSessionPool; // This should be set to enable debugging when we have test session info available. _debugEnabledForTestSession = debugEnabledForTestSession; @@ -391,7 +398,7 @@ public void Close() return; } - TestSessionPool.Instance.ReturnProxy(_testSessionInfo, _proxyOperationManager.Id); + (_testSessionPool ?? TestSessionPool.Instance).ReturnProxy(_testSessionInfo, _proxyOperationManager.Id); } /// diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/TestEngine.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/TestEngine.cs index cccb42fc4d..330ea6c43d 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/TestEngine.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/TestEngine.cs @@ -36,6 +36,7 @@ public class TestEngine : ITestEngine private readonly ITestRuntimeProviderManager _testHostProviderManager; private readonly IProcessHelper _processHelper; private readonly IEnvironment _environment; + private readonly TestSessionPool? _testSessionPool; private ITestExtensionManager? _testExtensionManager; @@ -54,11 +55,13 @@ protected internal TestEngine( internal TestEngine( ITestRuntimeProviderManager testHostProviderManager, IProcessHelper processHelper, - IEnvironment environment) + IEnvironment environment, + TestSessionPool? testSessionPool = null) { _testHostProviderManager = testHostProviderManager; _processHelper = processHelper; _environment = environment; + _testSessionPool = testSessionPool; } #region ITestEngine implementation @@ -150,7 +153,7 @@ public IProxyDiscoveryManager GetDiscoveryManager( // In case we have an active test session, we always prefer the already // created proxies instead of the ones that need to be created on the spot. - var proxyOperationManager = TestSessionPool.Instance.TryTakeProxy( + var proxyOperationManager = (_testSessionPool ?? TestSessionPool.Instance).TryTakeProxy( discoveryCriteria.TestSessionInfo, source, runtimeProviderInfo.RunSettings, @@ -184,7 +187,8 @@ public IProxyDiscoveryManager GetDiscoveryManager( ? new ProxyDiscoveryManager( discoveryCriteria.TestSessionInfo, proxyOperationManagerCreator, - discoveryDataAggregator) + discoveryDataAggregator, + _testSessionPool) : new ProxyDiscoveryManager( requestData, new TestRequestSender(requestData.ProtocolConfig!, hostManager), @@ -313,7 +317,7 @@ internal IProxyExecutionManager CreateNonParallelExecutionManager(IRequestData r string source, ProxyExecutionManager proxyExecutionManager) => { - var proxyOperationManager = TestSessionPool.Instance.TryTakeProxy( + var proxyOperationManager = (_testSessionPool ?? TestSessionPool.Instance).TryTakeProxy( testRunCriteria.TestSessionInfo, source, runtimeProviderInfo.RunSettings, @@ -348,7 +352,8 @@ internal IProxyExecutionManager CreateNonParallelExecutionManager(IRequestData r return new ProxyExecutionManager( testRunCriteria.TestSessionInfo, proxyOperationManagerCreator, - testRunCriteria.DebugEnabledForTestSession); + testRunCriteria.DebugEnabledForTestSession, + _testSessionPool); } return isDataCollectorEnabled @@ -457,7 +462,7 @@ internal IProxyExecutionManager CreateNonParallelExecutionManager(IRequestData r // can be smaller than the number of sources to run. var maxTesthostCount = isParallelRun ? testSessionCriteria.Sources.Count : 1; - return new ProxyTestSessionManager(testSessionCriteria, maxTesthostCount, proxyCreator, testRuntimeProviders) + return new ProxyTestSessionManager(testSessionCriteria, maxTesthostCount, proxyCreator, testRuntimeProviders, _testSessionPool) { // Individual proxy setup failures are tolerated since SetupChannel may fail if the // testhost it tries to start is not compatible with the test session feature. diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/ProxyTestSessionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/ProxyTestSessionManager.cs index fcc33d5a80..54d1a445ca 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/ProxyTestSessionManager.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/ProxyTestSessionManager.cs @@ -51,6 +51,7 @@ private enum TestSessionState private readonly IDictionary _proxyMap; private readonly Stopwatch _testSessionStopwatch; private readonly Dictionary _sourceToRuntimeProviderInfoMap; + private readonly TestSessionPool? _testSessionPool; private Dictionary _testSessionEnvironmentVariables = new(); internal ProxyDisposalOnCreationFailPolicy DisposalPolicy { get; set; } = ProxyDisposalOnCreationFailPolicy.DisposeAllOnFailure; @@ -82,11 +83,22 @@ public ProxyTestSessionManager( int maxTesthostCount, Func proxyCreator, List runtimeProviders) + : this(criteria, maxTesthostCount, proxyCreator, runtimeProviders, testSessionPool: null) + { + } + + internal ProxyTestSessionManager( + StartTestSessionCriteria criteria, + int maxTesthostCount, + Func proxyCreator, + List runtimeProviders, + TestSessionPool? testSessionPool) { _testSessionCriteria = criteria; _maxTesthostCount = maxTesthostCount; _proxyCreator = proxyCreator; _runtimeProviders = runtimeProviders; + _testSessionPool = testSessionPool; _proxyContainerList = new List(); _proxyMap = new Dictionary(); _testSessionStopwatch = new Stopwatch(); @@ -180,7 +192,7 @@ public virtual bool StartSession(ITestSessionEventsHandler eventsHandler, IReque } // Make the session available. - if (!TestSessionPool.Instance.AddSession(_testSessionInfo, this)) + if (!(_testSessionPool ?? TestSessionPool.Instance).AddSession(_testSessionInfo, this)) { requestData?.MetricsCollection.Add( TelemetryDataConstants.TestSessionState, diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyTestSessionManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyTestSessionManagerTests.cs index 4e9ff82e57..f5d431edbc 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyTestSessionManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyTestSessionManagerTests.cs @@ -312,6 +312,52 @@ public void StartSessionShouldFailIfAddSessionFails() Times.Never); } + [TestMethod] + public void StartSessionWritesToTheInjectedTestSessionPoolWhichTheReadSideObservesThroughTheSameInstance() + { + // Point the static default at a separate pool. If the write side wrongly used the static + // instead of the injected instance (i.e. the writer and reader ended up on different + // instances), the read side below would observe nothing. + var staticPool = new TestSessionPool(); + TestSessionPool.Instance = staticPool; + + // The single pool instance shared by the write side (StartSession -> AddSession) and the + // read side (TryTakeProxy) in this test. + var injectedPool = new TestSessionPool(); + + var mockProxyOperationManager = new Mock(null, null, null, null); + mockProxyOperationManager.Setup(pom => pom.SetupChannel(It.IsAny>(), It.IsAny())) + .Returns(true); + + TestSessionInfo? sessionInfo = null; + _mockEventsHandler + .Setup(eh => eh.HandleStartTestSessionComplete(It.IsAny())) + .Callback((StartTestSessionCompleteEventArgs args) => sessionInfo = args.TestSessionInfo); + + var testSessionCriteria = CreateTestSession(_fakeTestSources, _runSettingsNoEnvVars); + var proxyManager = CreateProxy(testSessionCriteria, mockProxyOperationManager.Object, injectedPool); + + // Write side: StartSession adds the session (and its proxy) to the injected pool. + Assert.IsTrue(proxyManager.StartSession(_mockEventsHandler.Object, _mockRequestData.Object)); + Assert.IsNotNull(sessionInfo); + + // Read side: taking a proxy from the SAME injected instance observes what the writer added. + var proxy = injectedPool.TryTakeProxy( + sessionInfo, + testSessionCriteria.Sources![0], + testSessionCriteria.RunSettings, + _mockRequestData.Object); + Assert.AreSame(mockProxyOperationManager.Object, proxy); + + // The separate static default never received the session, proving the writer and reader + // shared the injected instance rather than silently falling back to the static. + Assert.IsNull(staticPool.TryTakeProxy( + sessionInfo, + testSessionCriteria.Sources[0], + testSessionCriteria.RunSettings, + _mockRequestData.Object)); + } + [TestMethod] public void StopSessionShouldSucceedIfCalledOnlyOnce() { @@ -578,7 +624,8 @@ private static StartTestSessionCriteria CreateTestSession(IList sources, private ProxyTestSessionManager CreateProxy( StartTestSessionCriteria testSessionCriteria, - ProxyOperationManager proxyOperationManager) + ProxyOperationManager proxyOperationManager, + TestSessionPool? testSessionPool = null) { var runSettings = testSessionCriteria.RunSettings ?? _fakeRunSettings; var runtimeProviderInfo = new TestRuntimeProviderInfo @@ -599,7 +646,8 @@ private ProxyTestSessionManager CreateProxy( testSessionCriteria, testSessionCriteria.Sources!.Count, _ => proxyOperationManager, - runtimeProviders + runtimeProviders, + testSessionPool ); } From 25d36339f1beff93fc76a4fe8ede4ced6b832ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Tue, 7 Jul 2026 13:41:17 +0200 Subject: [PATCH 19/87] Fix test output eaten by MSBuild terminal logger when TestCaptureOutput=false (#16223) When TestCaptureOutput=false (set in Directory.Build.props to show test output live) and the MSBuild terminal logger is active, the terminal logger buffers all MSBuild subprocess output and shows 'See log file: ' messages at the end. Since TestCaptureOutput=false means no log files are created, these paths point to files that don't exist, leaving developers with no test output. Fix: disable the MSBuild terminal logger in test.sh and test.cmd by default via the MSBUILDTERMINALLOGGER environment variable. Developers who prefer the terminal logger UI can override this by setting MSBUILDTERMINALLOGGER=auto before invoking the test scripts. Fixes #15509 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- test.cmd | 1 + test.sh | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/test.cmd b/test.cmd index c879b9d2c3..8045e25dc3 100644 --- a/test.cmd +++ b/test.cmd @@ -1,3 +1,4 @@ @echo off +if not defined MSBUILDTERMINALLOGGER set MSBUILDTERMINALLOGGER=off powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0eng\Build.ps1""" -test %*" exit /b %ErrorLevel% diff --git a/test.sh b/test.sh index 7f61c8be87..1cb02b0090 100755 --- a/test.sh +++ b/test.sh @@ -16,4 +16,8 @@ scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" if [[ -z "${DOTNET_ROOT:-}" && -d "$scriptroot/.dotnet" ]]; then export DOTNET_ROOT="$scriptroot/.dotnet" fi +# Disable MSBuild terminal logger so test output isn't buffered. TestCaptureOutput=false +# means there are no log files to reference, so the terminal logger would show +# misleading "See log file" messages with paths to files that don't exist. +export MSBUILDTERMINALLOGGER=${MSBUILDTERMINALLOGGER:-off} "$scriptroot/eng/common/build.sh" --test "$@" From b59dd0a26ff2eae0968da1f74b813c9a0d97ce37 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Tue, 7 Jul 2026 12:41:40 +0100 Subject: [PATCH 20/87] [main] Source code updates from dotnet/dotnet (#16219) * Backflow from https://github.com/dotnet/dotnet / b4b350a build 321469 Diff: https://github.com/dotnet/dotnet/compare/90d7fc496e355b1cb5a23684fc538e5f04fd3803..b4b350a66ea5dcf13420747036d8b263cdf6cbef From: https://github.com/dotnet/dotnet/commit/90d7fc496e355b1cb5a23684fc538e5f04fd3803 To: https://github.com/dotnet/dotnet/commit/b4b350a66ea5dcf13420747036d8b263cdf6cbef [[ commit created by automation ]] * Update dependencies from build 321469 Updated Dependencies: Microsoft.Diagnostics.NETCore.Client (Version 0.2.0-preview.26328.102 -> 0.2.0-preview.26355.102) [[ commit created by automation ]] --------- Co-authored-by: dotnet-maestro[bot] --- NuGet.config | 4 ++++ eng/Version.Details.props | 2 +- eng/Version.Details.xml | 6 +++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/NuGet.config b/NuGet.config index 2bc08e561a..498b314d98 100644 --- a/NuGet.config +++ b/NuGet.config @@ -34,4 +34,8 @@ + + + + diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 48101843ed..dae28768d7 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -10,7 +10,7 @@ This file should be imported by eng/Versions.props 2.0.0 - 0.2.0-preview.26328.102 + 0.2.0-preview.26355.102 6.0.2 10.0.0 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 2425b79801..48ddd60efb 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,14 +1,14 @@ - + https://dev.azure.com/devdiv/DevDiv/_git/vs-code-coverage 721d283a3c2f250c9d24decdc733f8dd9a2cfc48 - + https://github.com/dotnet/dotnet - 7c528f6e19c5245206de3dc561eb8a110bf9f746 + b4b350a66ea5dcf13420747036d8b263cdf6cbef From 979ac225c5fad45957068f05c0342d893d4e20f5 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Tue, 7 Jul 2026 12:41:45 +0100 Subject: [PATCH 21/87] Update dependencies from https://dev.azure.com/devdiv/DevDiv/_git/vs-code-coverage build 20260703.4 (#16218) On relative base path root Microsoft.Internal.CodeCoverage From Version 18.9.0-preview.26326.3 -> To Version 18.9.0-preview.26353.4 Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.props | 2 +- eng/Version.Details.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eng/Version.Details.props b/eng/Version.Details.props index dae28768d7..81acb719e1 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -18,7 +18,7 @@ This file should be imported by eng/Versions.props 1.1.0-beta2-19575-01 1.1.0-beta2-19575-01 - 18.9.0-preview.26326.3 + 18.9.0-preview.26353.4 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 48ddd60efb..e62bd955ed 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -2,9 +2,9 @@ - + https://dev.azure.com/devdiv/DevDiv/_git/vs-code-coverage - 721d283a3c2f250c9d24decdc733f8dd9a2cfc48 + 4f52ad52b601270ab25335be7889019de223e3c4 https://github.com/dotnet/dotnet From 729dbbf56b9801f85eee12b96dd2dbccbc9221ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Tue, 7 Jul 2026 13:44:50 +0200 Subject: [PATCH 22/87] Inject CommandLineOptions into vstest.console argument processors (#16208) * Inject CommandLineOptions into vstest.console argument processors The argument processors reach for CommandLineOptions.Instance when they construct their executors, so the request-scoped command-line state (target framework, platform, test adapter paths, and the rest) is shared process-wide static state that leaks across requests in design mode. This threads CommandLineOptions through the composition roots the same way as the IRunSettingsProvider and IRunSettingsHelper work in #16200 and #16205, defaulting to CommandLineOptions.Instance so behavior is unchanged. - ArgumentProcessorFactory.Create(...) takes an optional CommandLineOptions and passes it into every default processor as the first constructor argument; Executor owns it and passes it in, defaulting to CommandLineOptions.Instance. - Each processor now holds an injected CommandLineOptions field instead of reading the static, and hands it to the executor it builds. - TestRequestManager and ConsoleLogger construct or read CommandLineOptions outside the processor path, so they stay on the .Instance fallback (same shared instance) for now. CommandLineOptions.Instance is not obsoleted; the remaining references are the composition-root defaults. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Re-run CI (flaky CrashDumpOnStackOverflow, unrelated) The Windows Integration Test leg failed only on CrashDumpOnStackOverflow with 'Expected at least 1 dump file in Attachments, but there were 0' -- a procdump capture race in the separate datacollector process, which does not consume the argument-processor CommandLineOptions this change threads. Empty commit to re-trigger CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/vstest.console/CommandLine/Executor.cs | 10 +-- .../ArtifactProcessingCollectModeProcessor.cs | 8 ++- ...ifactProcessingPostProcessModeProcessor.cs | 10 ++- .../CLIRunSettingsArgumentProcessor.cs | 6 +- .../DisableAutoFakesArgumentProcessor.cs | 8 ++- .../EnableCodeCoverageArgumentProcessor.cs | 6 +- .../EnvironmentArgumentProcessor.cs | 6 +- .../Processors/FrameworkArgumentProcessor.cs | 6 +- .../InIsolationArgumentProcessor.cs | 6 +- ...istFullyQualifiedTestsArgumentProcessor.cs | 6 +- .../Processors/ListTestsArgumentProcessor.cs | 6 +- .../ListTestsTargetPathArgumentProcessor.cs | 8 ++- .../Processors/ParallelArgumentProcessor.cs | 6 +- .../ParentProcessIdArgumentProcessor.cs | 8 ++- .../Processors/PlatformArgumentProcessor.cs | 6 +- .../Processors/PortArgumentProcessor.cs | 6 +- .../ResultsDirectoryArgumentProcessor.cs | 6 +- .../RunSettingsArgumentProcessor.cs | 6 +- .../RunSpecificTestsArgumentProcessor.cs | 10 +-- .../Processors/RunTestsArgumentProcessor.cs | 10 +-- ...AdapterLoadingStrategyArgumentProcessor.cs | 6 +- .../TestAdapterPathArgumentProcessor.cs | 6 +- .../TestCaseFilterArgumentProcessor.cs | 8 ++- .../TestSessionCorrelationIdProcessor.cs | 8 ++- .../Processors/TestSourceArgumentProcessor.cs | 8 ++- .../UseVsixExtensionsArgumentProcessor.cs | 8 ++- .../Utilities/ArgumentProcessorFactory.cs | 62 ++++++++++--------- .../CLIRunSettingsArgumentProcessorTests.cs | 4 +- .../DisableAutoFakesArgumentProcessorTests.cs | 2 +- ...nableCodeCoverageArgumentProcessorTests.cs | 4 +- .../FrameworkArgumentProcessorTests.cs | 4 +- .../InIsolationArgumentProcessorTests.cs | 6 +- ...llyQualifiedTestsArgumentProcessorTests.cs | 4 +- .../ListTestsArgumentProcessorTests.cs | 4 +- ...stTestsTargetPathArgumentProcessorTests.cs | 4 +- .../ParallelArgumentProcessorTests.cs | 4 +- .../ParentProcessIdArgumentProcessorTests.cs | 4 +- .../PlatformArgumentProcessorTests.cs | 4 +- .../Processors/PortArgumentProcessorTests.cs | 4 +- .../ResultsDirectoryArgumentProcessorTests.cs | 4 +- .../RunSettingsArgumentProcessorTests.cs | 4 +- .../RunSpecificTestsArgumentProcessorTests.cs | 4 +- .../RunTestsArgumentProcessorTests.cs | 4 +- .../TestAdapterPathArgumentProcessorTests.cs | 4 +- .../TestCaseFilterArgumentProcessorTests.cs | 4 +- .../TestSourceArgumentProcessorTests.cs | 4 +- ...UseVsixExtensionsArgumentProcessorTests.cs | 4 +- .../ArgumentProcessorFactoryTests.cs | 28 ++++++--- 48 files changed, 231 insertions(+), 127 deletions(-) diff --git a/src/vstest.console/CommandLine/Executor.cs b/src/vstest.console/CommandLine/Executor.cs index a2daebfb8c..f16bacdfff 100644 --- a/src/vstest.console/CommandLine/Executor.cs +++ b/src/vstest.console/CommandLine/Executor.cs @@ -64,6 +64,7 @@ internal class Executor private readonly IEnvironment _environment; private readonly IRunSettingsProvider _runSettingsProvider; private readonly IRunSettingsHelper _runSettingsHelper; + private readonly CommandLineOptions _commandLineOptions; private bool _showHelp; /// @@ -94,16 +95,16 @@ internal class Executor } internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSource, IProcessHelper processHelper, IEnvironment environment) - : this(output, testPlatformEventSource, processHelper, environment, RunSettingsManager.Instance, RunSettingsHelper.Instance) + : this(output, testPlatformEventSource, processHelper, environment, RunSettingsManager.Instance, RunSettingsHelper.Instance, CommandLineOptions.Instance) { } internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSource, IProcessHelper processHelper, IEnvironment environment, IRunSettingsProvider runSettingsProvider) - : this(output, testPlatformEventSource, processHelper, environment, runSettingsProvider, RunSettingsHelper.Instance) + : this(output, testPlatformEventSource, processHelper, environment, runSettingsProvider, RunSettingsHelper.Instance, CommandLineOptions.Instance) { } - internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSource, IProcessHelper processHelper, IEnvironment environment, IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper) + internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSource, IProcessHelper processHelper, IEnvironment environment, IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper, CommandLineOptions commandLineOptions) { DebuggerBreakpoint.AttachVisualStudioDebugger(WellKnownDebugEnvironmentVariables.VSTEST_RUNNER_DEBUG_ATTACHVS); DebuggerBreakpoint.WaitForNativeDebugger(WellKnownDebugEnvironmentVariables.VSTEST_RUNNER_NATIVE_DEBUG); @@ -116,6 +117,7 @@ internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSour _environment = environment; _runSettingsProvider = runSettingsProvider; _runSettingsHelper = runSettingsHelper; + _commandLineOptions = commandLineOptions; } /// @@ -237,7 +239,7 @@ private int GetArgumentProcessors(string[] args, out List pr { processors = new List(); int result = 0; - var processorFactory = ArgumentProcessorFactory.Create(runSettingsProvider: _runSettingsProvider, runSettingsHelper: _runSettingsHelper); + var processorFactory = ArgumentProcessorFactory.Create(runSettingsProvider: _runSettingsProvider, runSettingsHelper: _runSettingsHelper, commandLineOptions: _commandLineOptions); for (var index = 0; index < args.Length; index++) { var arg = args[index]; diff --git a/src/vstest.console/Processors/ArtifactProcessingCollectModeProcessor.cs b/src/vstest.console/Processors/ArtifactProcessingCollectModeProcessor.cs index 8dd1be8dd6..1040c85616 100644 --- a/src/vstest.console/Processors/ArtifactProcessingCollectModeProcessor.cs +++ b/src/vstest.console/Processors/ArtifactProcessingCollectModeProcessor.cs @@ -20,6 +20,12 @@ internal class ArtifactProcessingCollectModeProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly CommandLineOptions _commandLineOptions; + + public ArtifactProcessingCollectModeProcessor(CommandLineOptions commandLineOptions) + { + _commandLineOptions = commandLineOptions; + } /// /// Gets the metadata. @@ -34,7 +40,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new ArtifactProcessingCollectModeProcessorExecutor(CommandLineOptions.Instance)); + new ArtifactProcessingCollectModeProcessorExecutor(_commandLineOptions)); set => _executor = value; } diff --git a/src/vstest.console/Processors/ArtifactProcessingPostProcessModeProcessor.cs b/src/vstest.console/Processors/ArtifactProcessingPostProcessModeProcessor.cs index 0a446e1b61..a4a00bd425 100644 --- a/src/vstest.console/Processors/ArtifactProcessingPostProcessModeProcessor.cs +++ b/src/vstest.console/Processors/ArtifactProcessingPostProcessModeProcessor.cs @@ -24,6 +24,12 @@ internal class ArtifactProcessingPostProcessModeProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly CommandLineOptions _commandLineOptions; + + public ArtifactProcessingPostProcessModeProcessor(CommandLineOptions commandLineOptions) + { + _commandLineOptions = commandLineOptions; + } /// /// Gets the metadata. @@ -38,8 +44,8 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new ArtifactProcessingPostProcessModeProcessorExecutor(CommandLineOptions.Instance, - new ArtifactProcessingManager(CommandLineOptions.Instance.TestSessionCorrelationId))); + new ArtifactProcessingPostProcessModeProcessorExecutor(_commandLineOptions, + new ArtifactProcessingManager(_commandLineOptions.TestSessionCorrelationId))); set => _executor = value; } diff --git a/src/vstest.console/Processors/CLIRunSettingsArgumentProcessor.cs b/src/vstest.console/Processors/CLIRunSettingsArgumentProcessor.cs index a5ac978b14..87150208f0 100644 --- a/src/vstest.console/Processors/CLIRunSettingsArgumentProcessor.cs +++ b/src/vstest.console/Processors/CLIRunSettingsArgumentProcessor.cs @@ -30,10 +30,12 @@ internal class CliRunSettingsArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; + private readonly CommandLineOptions _commandLineOptions; private readonly IRunSettingsHelper _runSettingsHelper; - public CliRunSettingsArgumentProcessor(IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper) + public CliRunSettingsArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper) { + _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; _runSettingsHelper = runSettingsHelper; } @@ -51,7 +53,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new CliRunSettingsArgumentExecutor(_runSettingsProvider, CommandLineOptions.Instance, _runSettingsHelper)); + new CliRunSettingsArgumentExecutor(_runSettingsProvider, _commandLineOptions, _runSettingsHelper)); set => _executor = value; } diff --git a/src/vstest.console/Processors/DisableAutoFakesArgumentProcessor.cs b/src/vstest.console/Processors/DisableAutoFakesArgumentProcessor.cs index 4b128a0287..fe4b12bee7 100644 --- a/src/vstest.console/Processors/DisableAutoFakesArgumentProcessor.cs +++ b/src/vstest.console/Processors/DisableAutoFakesArgumentProcessor.cs @@ -17,11 +17,17 @@ internal class DisableAutoFakesArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly CommandLineOptions _commandLineOptions; + + public DisableAutoFakesArgumentProcessor(CommandLineOptions commandLineOptions) + { + _commandLineOptions = commandLineOptions; + } public Lazy? Executor { get => _executor ??= new Lazy(() => - new DisableAutoFakesArgumentExecutor(CommandLineOptions.Instance)); + new DisableAutoFakesArgumentExecutor(_commandLineOptions)); set => _executor = value; } diff --git a/src/vstest.console/Processors/EnableCodeCoverageArgumentProcessor.cs b/src/vstest.console/Processors/EnableCodeCoverageArgumentProcessor.cs index 861d14acfb..644839ed63 100644 --- a/src/vstest.console/Processors/EnableCodeCoverageArgumentProcessor.cs +++ b/src/vstest.console/Processors/EnableCodeCoverageArgumentProcessor.cs @@ -30,9 +30,11 @@ internal class EnableCodeCoverageArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; + private readonly CommandLineOptions _commandLineOptions; - public EnableCodeCoverageArgumentProcessor(IRunSettingsProvider runSettingsProvider) + public EnableCodeCoverageArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider) { + _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; } @@ -49,7 +51,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new EnableCodeCoverageArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider, new FileHelper())); + new EnableCodeCoverageArgumentExecutor(_commandLineOptions, _runSettingsProvider, new FileHelper())); set => _executor = value; } diff --git a/src/vstest.console/Processors/EnvironmentArgumentProcessor.cs b/src/vstest.console/Processors/EnvironmentArgumentProcessor.cs index deb175719a..4d713fe144 100644 --- a/src/vstest.console/Processors/EnvironmentArgumentProcessor.cs +++ b/src/vstest.console/Processors/EnvironmentArgumentProcessor.cs @@ -29,16 +29,18 @@ internal class EnvironmentArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; + private readonly CommandLineOptions _commandLineOptions; - public EnvironmentArgumentProcessor(IRunSettingsProvider runSettingsProvider) + public EnvironmentArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider) { + _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; } public Lazy? Executor { get => _executor ??= new Lazy(() => - new ArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider, ConsoleOutput.Instance)); + new ArgumentExecutor(_commandLineOptions, _runSettingsProvider, ConsoleOutput.Instance)); set => _executor = value; } diff --git a/src/vstest.console/Processors/FrameworkArgumentProcessor.cs b/src/vstest.console/Processors/FrameworkArgumentProcessor.cs index a8c11f4818..dbef135293 100644 --- a/src/vstest.console/Processors/FrameworkArgumentProcessor.cs +++ b/src/vstest.console/Processors/FrameworkArgumentProcessor.cs @@ -28,9 +28,11 @@ internal class FrameworkArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; + private readonly CommandLineOptions _commandLineOptions; - public FrameworkArgumentProcessor(IRunSettingsProvider runSettingsProvider) + public FrameworkArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider) { + _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; } @@ -47,7 +49,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new FrameworkArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider)); + new FrameworkArgumentExecutor(_commandLineOptions, _runSettingsProvider)); set => _executor = value; } diff --git a/src/vstest.console/Processors/InIsolationArgumentProcessor.cs b/src/vstest.console/Processors/InIsolationArgumentProcessor.cs index 55bef0523e..05b20cfe82 100644 --- a/src/vstest.console/Processors/InIsolationArgumentProcessor.cs +++ b/src/vstest.console/Processors/InIsolationArgumentProcessor.cs @@ -24,9 +24,11 @@ internal class InIsolationArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; + private readonly CommandLineOptions _commandLineOptions; - public InIsolationArgumentProcessor(IRunSettingsProvider runSettingsProvider) + public InIsolationArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider) { + _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; } @@ -43,7 +45,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new InIsolationArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider)); + new InIsolationArgumentExecutor(_commandLineOptions, _runSettingsProvider)); set => _executor = value; } diff --git a/src/vstest.console/Processors/ListFullyQualifiedTestsArgumentProcessor.cs b/src/vstest.console/Processors/ListFullyQualifiedTestsArgumentProcessor.cs index 7530d98028..0a6bf8d0b3 100644 --- a/src/vstest.console/Processors/ListFullyQualifiedTestsArgumentProcessor.cs +++ b/src/vstest.console/Processors/ListFullyQualifiedTestsArgumentProcessor.cs @@ -33,9 +33,11 @@ internal class ListFullyQualifiedTestsArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; + private readonly CommandLineOptions _commandLineOptions; - public ListFullyQualifiedTestsArgumentProcessor(IRunSettingsProvider runSettingsProvider) + public ListFullyQualifiedTestsArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider) { + _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; } @@ -53,7 +55,7 @@ public Lazy? Executor { get => _executor ??= new Lazy(() => new ListFullyQualifiedTestsArgumentExecutor( - CommandLineOptions.Instance, + _commandLineOptions, _runSettingsProvider, TestRequestManager.Instance)); diff --git a/src/vstest.console/Processors/ListTestsArgumentProcessor.cs b/src/vstest.console/Processors/ListTestsArgumentProcessor.cs index 3165c5f1e6..0c61873576 100644 --- a/src/vstest.console/Processors/ListTestsArgumentProcessor.cs +++ b/src/vstest.console/Processors/ListTestsArgumentProcessor.cs @@ -36,9 +36,11 @@ internal class ListTestsArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; + private readonly CommandLineOptions _commandLineOptions; - public ListTestsArgumentProcessor(IRunSettingsProvider runSettingsProvider) + public ListTestsArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider) { + _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; } @@ -56,7 +58,7 @@ public Lazy? Executor { get => _executor ??= new Lazy(() => new ListTestsArgumentExecutor( - CommandLineOptions.Instance, + _commandLineOptions, _runSettingsProvider, TestRequestManager.Instance)); diff --git a/src/vstest.console/Processors/ListTestsTargetPathArgumentProcessor.cs b/src/vstest.console/Processors/ListTestsTargetPathArgumentProcessor.cs index bbb0a8df8a..2a34308464 100644 --- a/src/vstest.console/Processors/ListTestsTargetPathArgumentProcessor.cs +++ b/src/vstest.console/Processors/ListTestsTargetPathArgumentProcessor.cs @@ -17,6 +17,12 @@ internal class ListTestsTargetPathArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly CommandLineOptions _commandLineOptions; + + public ListTestsTargetPathArgumentProcessor(CommandLineOptions commandLineOptions) + { + _commandLineOptions = commandLineOptions; + } /// /// Gets the metadata. @@ -31,7 +37,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new ListTestsTargetPathArgumentExecutor(CommandLineOptions.Instance)); + new ListTestsTargetPathArgumentExecutor(_commandLineOptions)); set => _executor = value; } diff --git a/src/vstest.console/Processors/ParallelArgumentProcessor.cs b/src/vstest.console/Processors/ParallelArgumentProcessor.cs index 51ca89909a..b7f750cc13 100644 --- a/src/vstest.console/Processors/ParallelArgumentProcessor.cs +++ b/src/vstest.console/Processors/ParallelArgumentProcessor.cs @@ -23,9 +23,11 @@ internal class ParallelArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; + private readonly CommandLineOptions _commandLineOptions; - public ParallelArgumentProcessor(IRunSettingsProvider runSettingsProvider) + public ParallelArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider) { + _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; } @@ -42,7 +44,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new ParallelArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider)); + new ParallelArgumentExecutor(_commandLineOptions, _runSettingsProvider)); set => _executor = value; } diff --git a/src/vstest.console/Processors/ParentProcessIdArgumentProcessor.cs b/src/vstest.console/Processors/ParentProcessIdArgumentProcessor.cs index 608f56a4e3..d03e9145f2 100644 --- a/src/vstest.console/Processors/ParentProcessIdArgumentProcessor.cs +++ b/src/vstest.console/Processors/ParentProcessIdArgumentProcessor.cs @@ -21,6 +21,12 @@ internal class ParentProcessIdArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly CommandLineOptions _commandLineOptions; + + public ParentProcessIdArgumentProcessor(CommandLineOptions commandLineOptions) + { + _commandLineOptions = commandLineOptions; + } /// /// Gets the metadata. @@ -35,7 +41,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new ParentProcessIdArgumentExecutor(CommandLineOptions.Instance)); + new ParentProcessIdArgumentExecutor(_commandLineOptions)); set => _executor = value; } diff --git a/src/vstest.console/Processors/PlatformArgumentProcessor.cs b/src/vstest.console/Processors/PlatformArgumentProcessor.cs index 5a1835be13..c0f442def1 100644 --- a/src/vstest.console/Processors/PlatformArgumentProcessor.cs +++ b/src/vstest.console/Processors/PlatformArgumentProcessor.cs @@ -29,10 +29,12 @@ internal class PlatformArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; + private readonly CommandLineOptions _commandLineOptions; private readonly IRunSettingsHelper _runSettingsHelper; - public PlatformArgumentProcessor(IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper) + public PlatformArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper) { + _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; _runSettingsHelper = runSettingsHelper; } @@ -50,7 +52,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new PlatformArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider, _runSettingsHelper)); + new PlatformArgumentExecutor(_commandLineOptions, _runSettingsProvider, _runSettingsHelper)); set => _executor = value; } diff --git a/src/vstest.console/Processors/PortArgumentProcessor.cs b/src/vstest.console/Processors/PortArgumentProcessor.cs index 0e12dc46cf..029a543b7e 100644 --- a/src/vstest.console/Processors/PortArgumentProcessor.cs +++ b/src/vstest.console/Processors/PortArgumentProcessor.cs @@ -31,9 +31,11 @@ internal class PortArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; private readonly IRunSettingsHelper _runSettingsHelper; + private readonly CommandLineOptions _commandLineOptions; - public PortArgumentProcessor(IRunSettingsHelper runSettingsHelper) + public PortArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsHelper runSettingsHelper) { + _commandLineOptions = commandLineOptions; _runSettingsHelper = runSettingsHelper; } @@ -49,7 +51,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new PortArgumentExecutor(CommandLineOptions.Instance, TestRequestManager.Instance, _runSettingsHelper)); + new PortArgumentExecutor(_commandLineOptions, TestRequestManager.Instance, _runSettingsHelper)); set => _executor = value; } diff --git a/src/vstest.console/Processors/ResultsDirectoryArgumentProcessor.cs b/src/vstest.console/Processors/ResultsDirectoryArgumentProcessor.cs index 1b4afe42c1..00867f7546 100644 --- a/src/vstest.console/Processors/ResultsDirectoryArgumentProcessor.cs +++ b/src/vstest.console/Processors/ResultsDirectoryArgumentProcessor.cs @@ -28,9 +28,11 @@ internal class ResultsDirectoryArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; + private readonly CommandLineOptions _commandLineOptions; - public ResultsDirectoryArgumentProcessor(IRunSettingsProvider runSettingsProvider) + public ResultsDirectoryArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider) { + _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; } @@ -47,7 +49,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new ResultsDirectoryArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider)); + new ResultsDirectoryArgumentExecutor(_commandLineOptions, _runSettingsProvider)); set => _executor = value; } diff --git a/src/vstest.console/Processors/RunSettingsArgumentProcessor.cs b/src/vstest.console/Processors/RunSettingsArgumentProcessor.cs index 053c8aa147..5a1c7c5a86 100644 --- a/src/vstest.console/Processors/RunSettingsArgumentProcessor.cs +++ b/src/vstest.console/Processors/RunSettingsArgumentProcessor.cs @@ -31,10 +31,12 @@ internal class RunSettingsArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; + private readonly CommandLineOptions _commandLineOptions; private readonly IRunSettingsHelper _runSettingsHelper; - public RunSettingsArgumentProcessor(IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper) + public RunSettingsArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper) { + _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; _runSettingsHelper = runSettingsHelper; } @@ -52,7 +54,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new RunSettingsArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider, _runSettingsHelper)); + new RunSettingsArgumentExecutor(_commandLineOptions, _runSettingsProvider, _runSettingsHelper)); set => _executor = value; } diff --git a/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs b/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs index 5212748023..50d5134bb3 100644 --- a/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs +++ b/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs @@ -31,9 +31,11 @@ internal class RunSpecificTestsArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; + private readonly CommandLineOptions _commandLineOptions; - public RunSpecificTestsArgumentProcessor(IRunSettingsProvider runSettingsProvider) + public RunSpecificTestsArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider) { + _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; } @@ -45,10 +47,10 @@ public Lazy? Executor { get => _executor ??= new Lazy(() => new RunSpecificTestsArgumentExecutor( - CommandLineOptions.Instance, + _commandLineOptions, _runSettingsProvider, TestRequestManager.Instance, - new ArtifactProcessingManager(CommandLineOptions.Instance.TestSessionCorrelationId), + new ArtifactProcessingManager(_commandLineOptions.TestSessionCorrelationId), ConsoleOutput.Instance)); set => _executor = value; @@ -370,7 +372,7 @@ private void TestRunRequest_OnRunCompletion(object? sender, TestRunCompleteEvent var testsFoundInAnySource = e.TestRunStatistics != null && (e.TestRunStatistics.ExecutedTests > 0); // Indicate the user to use testadapterpath command if there are no tests found - if (!testsFoundInAnySource && !CommandLineOptions.Instance.TestAdapterPathsSet && _commandLineOptions.TestCaseFilterValue == null) + if (!testsFoundInAnySource && !_commandLineOptions.TestAdapterPathsSet && _commandLineOptions.TestCaseFilterValue == null) { _output.Warning(false, CommandLineResources.SuggestTestAdapterPathIfNoTestsIsFound); } diff --git a/src/vstest.console/Processors/RunTestsArgumentProcessor.cs b/src/vstest.console/Processors/RunTestsArgumentProcessor.cs index 110ab00316..90f22cc0cb 100644 --- a/src/vstest.console/Processors/RunTestsArgumentProcessor.cs +++ b/src/vstest.console/Processors/RunTestsArgumentProcessor.cs @@ -27,9 +27,11 @@ internal class RunTestsArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; + private readonly CommandLineOptions _commandLineOptions; - public RunTestsArgumentProcessor(IRunSettingsProvider runSettingsProvider) + public RunTestsArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider) { + _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; } @@ -41,10 +43,10 @@ public Lazy? Executor { get => _executor ??= new Lazy(() => new RunTestsArgumentExecutor( - CommandLineOptions.Instance, + _commandLineOptions, _runSettingsProvider, TestRequestManager.Instance, - new ArtifactProcessingManager(CommandLineOptions.Instance.TestSessionCorrelationId), + new ArtifactProcessingManager(_commandLineOptions.TestSessionCorrelationId), ConsoleOutput.Instance)); set => _executor = value; @@ -230,7 +232,7 @@ private void TestRunRequest_OnRunCompletion(object? sender, TestRunCompleteEvent s_numberOfExecutedTests = e.TestRunStatistics!.ExecutedTests; // Indicate the user to use test adapter path command if there are no tests found - if (!testsFoundInAnySource && !CommandLineOptions.Instance.TestAdapterPathsSet && _commandLineOptions.TestCaseFilterValue == null) + if (!testsFoundInAnySource && !_commandLineOptions.TestAdapterPathsSet && _commandLineOptions.TestCaseFilterValue == null) { _output.Warning(false, CommandLineResources.SuggestTestAdapterPathIfNoTestsIsFound); } diff --git a/src/vstest.console/Processors/TestAdapterLoadingStrategyArgumentProcessor.cs b/src/vstest.console/Processors/TestAdapterLoadingStrategyArgumentProcessor.cs index 8cd8171be5..466f662654 100644 --- a/src/vstest.console/Processors/TestAdapterLoadingStrategyArgumentProcessor.cs +++ b/src/vstest.console/Processors/TestAdapterLoadingStrategyArgumentProcessor.cs @@ -30,9 +30,11 @@ internal class TestAdapterLoadingStrategyArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; + private readonly CommandLineOptions _commandLineOptions; - public TestAdapterLoadingStrategyArgumentProcessor(IRunSettingsProvider runSettingsProvider) + public TestAdapterLoadingStrategyArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider) { + _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; } @@ -49,7 +51,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new TestAdapterLoadingStrategyArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider, ConsoleOutput.Instance, new FileHelper())); + new TestAdapterLoadingStrategyArgumentExecutor(_commandLineOptions, _runSettingsProvider, ConsoleOutput.Instance, new FileHelper())); set => _executor = value; } diff --git a/src/vstest.console/Processors/TestAdapterPathArgumentProcessor.cs b/src/vstest.console/Processors/TestAdapterPathArgumentProcessor.cs index a8ff533592..60b5554ef8 100644 --- a/src/vstest.console/Processors/TestAdapterPathArgumentProcessor.cs +++ b/src/vstest.console/Processors/TestAdapterPathArgumentProcessor.cs @@ -30,9 +30,11 @@ internal class TestAdapterPathArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; + private readonly CommandLineOptions _commandLineOptions; - public TestAdapterPathArgumentProcessor(IRunSettingsProvider runSettingsProvider) + public TestAdapterPathArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider) { + _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; } @@ -49,7 +51,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new TestAdapterPathArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider, + new TestAdapterPathArgumentExecutor(_commandLineOptions, _runSettingsProvider, ConsoleOutput.Instance, new FileHelper())); set => _executor = value; diff --git a/src/vstest.console/Processors/TestCaseFilterArgumentProcessor.cs b/src/vstest.console/Processors/TestCaseFilterArgumentProcessor.cs index 1969e06d4e..60137ba687 100644 --- a/src/vstest.console/Processors/TestCaseFilterArgumentProcessor.cs +++ b/src/vstest.console/Processors/TestCaseFilterArgumentProcessor.cs @@ -22,6 +22,12 @@ internal class TestCaseFilterArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly CommandLineOptions _commandLineOptions; + + public TestCaseFilterArgumentProcessor(CommandLineOptions commandLineOptions) + { + _commandLineOptions = commandLineOptions; + } /// /// Gets the metadata. @@ -36,7 +42,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new TestCaseFilterArgumentExecutor(CommandLineOptions.Instance)); + new TestCaseFilterArgumentExecutor(_commandLineOptions)); set => _executor = value; } diff --git a/src/vstest.console/Processors/TestSessionCorrelationIdProcessor.cs b/src/vstest.console/Processors/TestSessionCorrelationIdProcessor.cs index 4a154ed560..e3f978f754 100644 --- a/src/vstest.console/Processors/TestSessionCorrelationIdProcessor.cs +++ b/src/vstest.console/Processors/TestSessionCorrelationIdProcessor.cs @@ -22,6 +22,12 @@ internal class TestSessionCorrelationIdProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly CommandLineOptions _commandLineOptions; + + public TestSessionCorrelationIdProcessor(CommandLineOptions commandLineOptions) + { + _commandLineOptions = commandLineOptions; + } /// /// Gets the metadata. @@ -36,7 +42,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new TestSessionCorrelationIdProcessorModeProcessorExecutor(CommandLineOptions.Instance)); + new TestSessionCorrelationIdProcessorModeProcessorExecutor(_commandLineOptions)); set => _executor = value; } diff --git a/src/vstest.console/Processors/TestSourceArgumentProcessor.cs b/src/vstest.console/Processors/TestSourceArgumentProcessor.cs index a3030d6de7..bb97f9f17b 100644 --- a/src/vstest.console/Processors/TestSourceArgumentProcessor.cs +++ b/src/vstest.console/Processors/TestSourceArgumentProcessor.cs @@ -19,6 +19,12 @@ internal class TestSourceArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly CommandLineOptions _commandLineOptions; + + public TestSourceArgumentProcessor(CommandLineOptions commandLineOptions) + { + _commandLineOptions = commandLineOptions; + } /// /// Gets the metadata. @@ -33,7 +39,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new TestSourceArgumentExecutor(CommandLineOptions.Instance)); + new TestSourceArgumentExecutor(_commandLineOptions)); set => _executor = value; } diff --git a/src/vstest.console/Processors/UseVsixExtensionsArgumentProcessor.cs b/src/vstest.console/Processors/UseVsixExtensionsArgumentProcessor.cs index ac4cb2055f..f481e43c7c 100644 --- a/src/vstest.console/Processors/UseVsixExtensionsArgumentProcessor.cs +++ b/src/vstest.console/Processors/UseVsixExtensionsArgumentProcessor.cs @@ -26,6 +26,12 @@ internal class UseVsixExtensionsArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; + private readonly CommandLineOptions _commandLineOptions; + + public UseVsixExtensionsArgumentProcessor(CommandLineOptions commandLineOptions) + { + _commandLineOptions = commandLineOptions; + } /// /// Gets the metadata. @@ -40,7 +46,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new UseVsixExtensionsArgumentExecutor(CommandLineOptions.Instance, TestRequestManager.Instance, new VSExtensionManager(), ConsoleOutput.Instance)); + new UseVsixExtensionsArgumentExecutor(_commandLineOptions, TestRequestManager.Instance, new VSExtensionManager(), ConsoleOutput.Instance)); set => _executor = value; } diff --git a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs index f0ad9115a6..878a3bd71c 100644 --- a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs +++ b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs @@ -58,18 +58,24 @@ protected ArgumentProcessorFactory(IEnumerable argumentProce /// Defaults to the ambient when not provided, so that /// callers (and the composition root) can inject an isolated instance instead of sharing static state. /// + /// + /// The command line options that the created argument processors read from and write to. + /// Defaults to the ambient when not provided, so that + /// callers (and the composition root) can inject an isolated instance instead of sharing static state. + /// /// ArgumentProcessorFactory. - internal static ArgumentProcessorFactory Create(IFeatureFlag? featureFlag = null, IRunSettingsProvider? runSettingsProvider = null, IRunSettingsHelper? runSettingsHelper = null) + internal static ArgumentProcessorFactory Create(IFeatureFlag? featureFlag = null, IRunSettingsProvider? runSettingsProvider = null, IRunSettingsHelper? runSettingsHelper = null, CommandLineOptions? commandLineOptions = null) { runSettingsProvider ??= RunSettingsManager.Instance; runSettingsHelper ??= RunSettingsHelper.Instance; - var defaultArgumentProcessor = GetDefaultArgumentProcessors(runSettingsProvider, runSettingsHelper); + commandLineOptions ??= CommandLineOptions.Instance; + var defaultArgumentProcessor = GetDefaultArgumentProcessors(runSettingsProvider, runSettingsHelper, commandLineOptions); if (!(featureFlag ?? FeatureFlag.Instance).IsSet(FeatureFlag.VSTEST_DISABLE_ARTIFACTS_POSTPROCESSING)) { - defaultArgumentProcessor.Add(new ArtifactProcessingCollectModeProcessor()); - defaultArgumentProcessor.Add(new ArtifactProcessingPostProcessModeProcessor()); - defaultArgumentProcessor.Add(new TestSessionCorrelationIdProcessor()); + defaultArgumentProcessor.Add(new ArtifactProcessingCollectModeProcessor(commandLineOptions)); + defaultArgumentProcessor.Add(new ArtifactProcessingPostProcessModeProcessor(commandLineOptions)); + defaultArgumentProcessor.Add(new TestSessionCorrelationIdProcessor(commandLineOptions)); } // Get the ArgumentProcessorFactory @@ -197,41 +203,41 @@ public IEnumerable GetArgumentProcessorsToAlwaysExecute() .Where(lazyProcessor => lazyProcessor.Metadata.Value.IsSpecialCommand && lazyProcessor.Metadata.Value.AlwaysExecute); } - private static IList GetDefaultArgumentProcessors(IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper) => new List { + private static IList GetDefaultArgumentProcessors(IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper, CommandLineOptions commandLineOptions) => new List { new HelpArgumentProcessor(), - new TestSourceArgumentProcessor(), - new ListTestsArgumentProcessor(runSettingsProvider), - new RunTestsArgumentProcessor(runSettingsProvider), - new RunSpecificTestsArgumentProcessor(runSettingsProvider), - new TestAdapterPathArgumentProcessor(runSettingsProvider), - new TestAdapterLoadingStrategyArgumentProcessor(runSettingsProvider), - new TestCaseFilterArgumentProcessor(), - new ParentProcessIdArgumentProcessor(), - new PortArgumentProcessor(runSettingsHelper), - new RunSettingsArgumentProcessor(runSettingsProvider, runSettingsHelper), - new PlatformArgumentProcessor(runSettingsProvider, runSettingsHelper), - new FrameworkArgumentProcessor(runSettingsProvider), + new TestSourceArgumentProcessor(commandLineOptions), + new ListTestsArgumentProcessor(commandLineOptions, runSettingsProvider), + new RunTestsArgumentProcessor(commandLineOptions, runSettingsProvider), + new RunSpecificTestsArgumentProcessor(commandLineOptions, runSettingsProvider), + new TestAdapterPathArgumentProcessor(commandLineOptions, runSettingsProvider), + new TestAdapterLoadingStrategyArgumentProcessor(commandLineOptions, runSettingsProvider), + new TestCaseFilterArgumentProcessor(commandLineOptions), + new ParentProcessIdArgumentProcessor(commandLineOptions), + new PortArgumentProcessor(commandLineOptions, runSettingsHelper), + new RunSettingsArgumentProcessor(commandLineOptions, runSettingsProvider, runSettingsHelper), + new PlatformArgumentProcessor(commandLineOptions, runSettingsProvider, runSettingsHelper), + new FrameworkArgumentProcessor(commandLineOptions, runSettingsProvider), new EnableLoggerArgumentProcessor(runSettingsProvider), - new ParallelArgumentProcessor(runSettingsProvider), + new ParallelArgumentProcessor(commandLineOptions, runSettingsProvider), new EnableDiagArgumentProcessor(), - new CliRunSettingsArgumentProcessor(runSettingsProvider, runSettingsHelper), - new ResultsDirectoryArgumentProcessor(runSettingsProvider), - new InIsolationArgumentProcessor(runSettingsProvider), + new CliRunSettingsArgumentProcessor(commandLineOptions, runSettingsProvider, runSettingsHelper), + new ResultsDirectoryArgumentProcessor(commandLineOptions, runSettingsProvider), + new InIsolationArgumentProcessor(commandLineOptions, runSettingsProvider), new CollectArgumentProcessor(runSettingsProvider), - new EnableCodeCoverageArgumentProcessor(runSettingsProvider), - new DisableAutoFakesArgumentProcessor(), + new EnableCodeCoverageArgumentProcessor(commandLineOptions, runSettingsProvider), + new DisableAutoFakesArgumentProcessor(commandLineOptions), new ResponseFileArgumentProcessor(), new EnableBlameArgumentProcessor(runSettingsProvider), new AeDebuggerArgumentProcessor(), - new UseVsixExtensionsArgumentProcessor(), + new UseVsixExtensionsArgumentProcessor(commandLineOptions), new ListDiscoverersArgumentProcessor(), new ListExecutorsArgumentProcessor(), new ListLoggersArgumentProcessor(), new ListSettingsProvidersArgumentProcessor(), - new ListFullyQualifiedTestsArgumentProcessor(runSettingsProvider), - new ListTestsTargetPathArgumentProcessor(), + new ListFullyQualifiedTestsArgumentProcessor(commandLineOptions, runSettingsProvider), + new ListTestsTargetPathArgumentProcessor(commandLineOptions), new ShowDeprecateDotnetVStestMessageArgumentProcessor(), - new EnvironmentArgumentProcessor(runSettingsProvider) + new EnvironmentArgumentProcessor(commandLineOptions, runSettingsProvider) }; /// diff --git a/test/vstest.console.UnitTests/Processors/CLIRunSettingsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/CLIRunSettingsArgumentProcessorTests.cs index 91a499962b..75b72edad2 100644 --- a/test/vstest.console.UnitTests/Processors/CLIRunSettingsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/CLIRunSettingsArgumentProcessorTests.cs @@ -72,14 +72,14 @@ public void Cleanup() [TestMethod] public void GetMetadataShouldReturnRunSettingsArgumentProcessorCapabilities() { - var processor = new CliRunSettingsArgumentProcessor(new TestableRunSettingsProvider(), _runSettingsHelper); + var processor = new CliRunSettingsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider(), _runSettingsHelper); Assert.IsTrue(processor.Metadata.Value is CliRunSettingsArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnRunSettingsArgumentProcessorCapabilities() { - var processor = new CliRunSettingsArgumentProcessor(new TestableRunSettingsProvider(), _runSettingsHelper); + var processor = new CliRunSettingsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider(), _runSettingsHelper); Assert.IsTrue(processor.Executor!.Value is CliRunSettingsArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/DisableAutoFakesArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/DisableAutoFakesArgumentProcessorTests.cs index 1e934f454f..d85307f85e 100644 --- a/test/vstest.console.UnitTests/Processors/DisableAutoFakesArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/DisableAutoFakesArgumentProcessorTests.cs @@ -14,7 +14,7 @@ public class DisableAutoFakesArgumentProcessorTests public DisableAutoFakesArgumentProcessorTests() { - _disableAutoFakesArgumentProcessor = new DisableAutoFakesArgumentProcessor(); + _disableAutoFakesArgumentProcessor = new DisableAutoFakesArgumentProcessor(CommandLineOptions.Instance); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/EnableCodeCoverageArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/EnableCodeCoverageArgumentProcessorTests.cs index 3eca670101..09ef544abf 100644 --- a/test/vstest.console.UnitTests/Processors/EnableCodeCoverageArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/EnableCodeCoverageArgumentProcessorTests.cs @@ -40,14 +40,14 @@ public EnableCodeCoverageArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnEnableCodeCoverageArgumentProcessorCapabilities() { - var processor = new EnableCodeCoverageArgumentProcessor(new TestableRunSettingsProvider()); + var processor = new EnableCodeCoverageArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is EnableCodeCoverageArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnEnableCodeCoverageArgumentProcessorCapabilities() { - var processor = new EnableCodeCoverageArgumentProcessor(new TestableRunSettingsProvider()); + var processor = new EnableCodeCoverageArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is EnableCodeCoverageArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/FrameworkArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/FrameworkArgumentProcessorTests.cs index 6e2a05fad0..ded2a9674d 100644 --- a/test/vstest.console.UnitTests/Processors/FrameworkArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/FrameworkArgumentProcessorTests.cs @@ -32,14 +32,14 @@ public void TestCleanup() [TestMethod] public void GetMetadataShouldReturnFrameworkArgumentProcessorCapabilities() { - var processor = new FrameworkArgumentProcessor(new TestableRunSettingsProvider()); + var processor = new FrameworkArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is FrameworkArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnFrameworkArgumentExecutor() { - var processor = new FrameworkArgumentProcessor(new TestableRunSettingsProvider()); + var processor = new FrameworkArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is FrameworkArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/InIsolationArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/InIsolationArgumentProcessorTests.cs index f512082204..b7393870ae 100644 --- a/test/vstest.console.UnitTests/Processors/InIsolationArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/InIsolationArgumentProcessorTests.cs @@ -32,21 +32,21 @@ public void TestCleanup() [TestMethod] public void GetMetadataShouldReturnInProcessArgumentProcessorCapabilities() { - var processor = new InIsolationArgumentProcessor(new TestableRunSettingsProvider()); + var processor = new InIsolationArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is InIsolationArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnInProcessArgumentExecutor() { - var processor = new InIsolationArgumentProcessor(new TestableRunSettingsProvider()); + var processor = new InIsolationArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is InIsolationArgumentExecutor); } [TestMethod] public void InIsolationArgumentProcessorMetadataShouldProvideAppropriateCapabilities() { - var isolationProcessor = new InIsolationArgumentProcessor(new TestableRunSettingsProvider()); + var isolationProcessor = new InIsolationArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); Assert.IsFalse(isolationProcessor.Metadata.Value.AllowMultiple); Assert.IsFalse(isolationProcessor.Metadata.Value.AlwaysExecute); Assert.IsFalse(isolationProcessor.Metadata.Value.IsAction); diff --git a/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs index a01c48e419..7cd2d85651 100644 --- a/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs @@ -95,7 +95,7 @@ public ListFullyQualifiedTestsArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnListFullyQualifiedTestsArgumentProcessorCapabilities() { - var processor = new ListFullyQualifiedTestsArgumentProcessor(new TestableRunSettingsProvider()); + var processor = new ListFullyQualifiedTestsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is ListFullyQualifiedTestsArgumentProcessorCapabilities); } @@ -105,7 +105,7 @@ public void GetMetadataShouldReturnListFullyQualifiedTestsArgumentProcessorCapab [TestMethod] public void GetExecuterShouldReturnListFullyQualifiedTestsArgumentProcessorCapabilities() { - var processor = new ListFullyQualifiedTestsArgumentProcessor(new TestableRunSettingsProvider()); + var processor = new ListFullyQualifiedTestsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is ListFullyQualifiedTestsArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs index 1f7263f837..735ddb1a1a 100644 --- a/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs @@ -93,7 +93,7 @@ public ListTestsArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnListTestsArgumentProcessorCapabilities() { - var processor = new ListTestsArgumentProcessor(new TestableRunSettingsProvider()); + var processor = new ListTestsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is ListTestsArgumentProcessorCapabilities); } @@ -103,7 +103,7 @@ public void GetMetadataShouldReturnListTestsArgumentProcessorCapabilities() [TestMethod] public void GetExecuterShouldReturnListTestsArgumentProcessorCapabilities() { - var processor = new ListTestsArgumentProcessor(new TestableRunSettingsProvider()); + var processor = new ListTestsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is ListTestsArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/ListTestsTargetPathArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ListTestsTargetPathArgumentProcessorTests.cs index 9ed9f22bb6..ce15ef079e 100644 --- a/test/vstest.console.UnitTests/Processors/ListTestsTargetPathArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ListTestsTargetPathArgumentProcessorTests.cs @@ -14,14 +14,14 @@ public class ListTestsTargetPathArgumentProcessorTests [TestMethod] public void GetMetadataShouldReturnListTestsTargetPathArgumentProcessorCapabilities() { - ListTestsTargetPathArgumentProcessor processor = new(); + ListTestsTargetPathArgumentProcessor processor = new(CommandLineOptions.Instance); Assert.IsTrue(processor.Metadata.Value is ListTestsTargetPathArgumentProcessorCapabilities); } [TestMethod] public void GetExecutorShouldReturnListTestsTargetPathArgumentProcessorCapabilities() { - ListTestsTargetPathArgumentProcessor processor = new(); + ListTestsTargetPathArgumentProcessor processor = new(CommandLineOptions.Instance); Assert.IsTrue(processor.Executor!.Value is ListTestsTargetPathArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/ParallelArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ParallelArgumentProcessorTests.cs index 227e0297d5..e997e6c484 100644 --- a/test/vstest.console.UnitTests/Processors/ParallelArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ParallelArgumentProcessorTests.cs @@ -29,14 +29,14 @@ public void TestCleanup() [TestMethod] public void GetMetadataShouldReturnParallelArgumentProcessorCapabilities() { - var processor = new ParallelArgumentProcessor(new TestableRunSettingsProvider()); + var processor = new ParallelArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is ParallelArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnParallelArgumentExecutor() { - var processor = new ParallelArgumentProcessor(new TestableRunSettingsProvider()); + var processor = new ParallelArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is ParallelArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/ParentProcessIdArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ParentProcessIdArgumentProcessorTests.cs index 08ab0db5ad..7a2edfbf95 100644 --- a/test/vstest.console.UnitTests/Processors/ParentProcessIdArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ParentProcessIdArgumentProcessorTests.cs @@ -15,14 +15,14 @@ public class ParentProcessIdArgumentProcessorTests [TestMethod] public void GetMetadataShouldReturnParentProcessIdArgumentProcessorCapabilities() { - var processor = new ParentProcessIdArgumentProcessor(); + var processor = new ParentProcessIdArgumentProcessor(CommandLineOptions.Instance); Assert.IsTrue(processor.Metadata.Value is ParentProcessIdArgumentProcessorCapabilities); } [TestMethod] public void GetExecutorShouldReturnParentProcessIdArgumentProcessorCapabilities() { - var processor = new ParentProcessIdArgumentProcessor(); + var processor = new ParentProcessIdArgumentProcessor(CommandLineOptions.Instance); Assert.IsTrue(processor.Executor!.Value is ParentProcessIdArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs index 22091e8bd2..cb4dfbbbc8 100644 --- a/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs @@ -36,14 +36,14 @@ public void TestCleanup() [TestMethod] public void GetMetadataShouldReturnPlatformArgumentProcessorCapabilities() { - var processor = new PlatformArgumentProcessor(new TestableRunSettingsProvider(), _runSettingsHelper); + var processor = new PlatformArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider(), _runSettingsHelper); Assert.IsTrue(processor.Metadata.Value is PlatformArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnPlatformArgumentExecutor() { - var processor = new PlatformArgumentProcessor(new TestableRunSettingsProvider(), _runSettingsHelper); + var processor = new PlatformArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider(), _runSettingsHelper); Assert.IsTrue(processor.Executor!.Value is PlatformArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs index 45b694314e..0208a8387c 100644 --- a/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs @@ -40,14 +40,14 @@ public PortArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnPortArgumentProcessorCapabilities() { - var processor = new PortArgumentProcessor(_runSettingsHelper); + var processor = new PortArgumentProcessor(CommandLineOptions.Instance, _runSettingsHelper); Assert.IsTrue(processor.Metadata.Value is PortArgumentProcessorCapabilities); } [TestMethod] public void GetExecutorShouldReturnPortArgumentProcessorCapabilities() { - var processor = new PortArgumentProcessor(_runSettingsHelper); + var processor = new PortArgumentProcessor(CommandLineOptions.Instance, _runSettingsHelper); Assert.IsTrue(processor.Executor!.Value is PortArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/ResultsDirectoryArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ResultsDirectoryArgumentProcessorTests.cs index bca6d8c828..a199088a72 100644 --- a/test/vstest.console.UnitTests/Processors/ResultsDirectoryArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ResultsDirectoryArgumentProcessorTests.cs @@ -34,14 +34,14 @@ public void TestCleanup() [TestMethod] public void GetMetadataShouldReturnResultsDirectoryArgumentProcessorCapabilities() { - var processor = new ResultsDirectoryArgumentProcessor(new TestableRunSettingsProvider()); + var processor = new ResultsDirectoryArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is ResultsDirectoryArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnResultsDirectoryArgumentExecutor() { - var processor = new ResultsDirectoryArgumentProcessor(new TestableRunSettingsProvider()); + var processor = new ResultsDirectoryArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is ResultsDirectoryArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessorTests.cs index 3067f333a4..6c752b55ff 100644 --- a/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessorTests.cs @@ -41,14 +41,14 @@ public void TestCleanup() [TestMethod] public void GetMetadataShouldReturnRunSettingsArgumentProcessorCapabilities() { - var processor = new RunSettingsArgumentProcessor(new TestableRunSettingsProvider(), new RunSettingsHelper()); + var processor = new RunSettingsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider(), new RunSettingsHelper()); Assert.IsTrue(processor.Metadata.Value is RunSettingsArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnRunSettingsArgumentExecutor() { - var processor = new RunSettingsArgumentProcessor(new TestableRunSettingsProvider(), new RunSettingsHelper()); + var processor = new RunSettingsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider(), new RunSettingsHelper()); Assert.IsTrue(processor.Executor!.Value is RunSettingsArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs index 6a8b1e3c0a..1a0a2848bf 100644 --- a/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs @@ -81,7 +81,7 @@ public RunSpecificTestsArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnRunSpecificTestsArgumentProcessorCapabilities() { - RunSpecificTestsArgumentProcessor processor = new(new TestableRunSettingsProvider()); + RunSpecificTestsArgumentProcessor processor = new(CommandLineOptions.Instance, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is RunSpecificTestsArgumentProcessorCapabilities); } @@ -89,7 +89,7 @@ public void GetMetadataShouldReturnRunSpecificTestsArgumentProcessorCapabilities [TestMethod] public void GetExecutorShouldReturnRunSpecificTestsArgumentExecutor() { - RunSpecificTestsArgumentProcessor processor = new(new TestableRunSettingsProvider()); + RunSpecificTestsArgumentProcessor processor = new(CommandLineOptions.Instance, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is RunSpecificTestsArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs index bb0a2b92f1..5e95a262e6 100644 --- a/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs @@ -79,14 +79,14 @@ public RunTestsArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnRunTestsArgumentProcessorCapabilities() { - RunTestsArgumentProcessor processor = new(new TestableRunSettingsProvider()); + RunTestsArgumentProcessor processor = new(CommandLineOptions.Instance, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is RunTestsArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnRunTestsArgumentProcessorCapabilities() { - RunTestsArgumentProcessor processor = new(new TestableRunSettingsProvider()); + RunTestsArgumentProcessor processor = new(CommandLineOptions.Instance, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is RunTestsArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs index 8d28fd0016..77731e299c 100644 --- a/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs @@ -43,14 +43,14 @@ public void TestClean() [TestMethod] public void GetMetadataShouldReturnTestAdapterPathArgumentProcessorCapabilities() { - var processor = new TestAdapterPathArgumentProcessor(new TestableRunSettingsProvider()); + var processor = new TestAdapterPathArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is TestAdapterPathArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnTestAdapterPathArgumentProcessorCapabilities() { - var processor = new TestAdapterPathArgumentProcessor(new TestableRunSettingsProvider()); + var processor = new TestAdapterPathArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is TestAdapterPathArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/TestCaseFilterArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/TestCaseFilterArgumentProcessorTests.cs index 52231ae264..389eb56d91 100644 --- a/test/vstest.console.UnitTests/Processors/TestCaseFilterArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/TestCaseFilterArgumentProcessorTests.cs @@ -14,14 +14,14 @@ public class TestCaseFilterArgumentProcessorTests [TestMethod] public void GetMetadataShouldReturnTestCaseFilterArgumentProcessorCapabilities() { - TestCaseFilterArgumentProcessor processor = new(); + TestCaseFilterArgumentProcessor processor = new(CommandLineOptions.Instance); Assert.IsTrue(processor.Metadata.Value is TestCaseFilterArgumentProcessorCapabilities); } [TestMethod] public void GetExecutorShouldReturnTestCaseFilterArgumentProcessorCapabilities() { - TestCaseFilterArgumentProcessor processor = new(); + TestCaseFilterArgumentProcessor processor = new(CommandLineOptions.Instance); Assert.IsTrue(processor.Executor!.Value is TestCaseFilterArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/TestSourceArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/TestSourceArgumentProcessorTests.cs index e75b58aa40..4b65032857 100644 --- a/test/vstest.console.UnitTests/Processors/TestSourceArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/TestSourceArgumentProcessorTests.cs @@ -26,7 +26,7 @@ public class TestSourceArgumentProcessorTests [TestMethod] public void GetMetadataShouldReturnTestSourceArgumentProcessorCapabilities() { - TestSourceArgumentProcessor processor = new(); + TestSourceArgumentProcessor processor = new(CommandLineOptions.Instance); Assert.IsTrue(processor.Metadata.Value is TestSourceArgumentProcessorCapabilities); } @@ -36,7 +36,7 @@ public void GetMetadataShouldReturnTestSourceArgumentProcessorCapabilities() [TestMethod] public void GetExecuterShouldReturnTestSourceArgumentProcessorCapabilities() { - TestSourceArgumentProcessor processor = new(); + TestSourceArgumentProcessor processor = new(CommandLineOptions.Instance); Assert.IsTrue(processor.Executor!.Value is TestSourceArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/UseVsixExtensionsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/UseVsixExtensionsArgumentProcessorTests.cs index 08988f689b..66c096e5cc 100644 --- a/test/vstest.console.UnitTests/Processors/UseVsixExtensionsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/UseVsixExtensionsArgumentProcessorTests.cs @@ -34,14 +34,14 @@ public UseVsixExtensionsArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnUseVsixExtensionsArgumentProcessorCapabilities() { - var processor = new UseVsixExtensionsArgumentProcessor(); + var processor = new UseVsixExtensionsArgumentProcessor(CommandLineOptions.Instance); Assert.IsTrue(processor.Metadata.Value is UseVsixExtensionsArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnUseVsixExtensionsArgumentProcessorCapabilities() { - var processor = new UseVsixExtensionsArgumentProcessor(); + var processor = new UseVsixExtensionsArgumentProcessor(CommandLineOptions.Instance); Assert.IsTrue(processor.Executor!.Value is UseVsixExtensionsArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs b/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs index 6ae5264754..48aa90a3a2 100644 --- a/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs +++ b/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs @@ -169,18 +169,28 @@ private static IEnumerable GetArgumentProcessors(bool specia foreach (var processor in allProcessors) { - // Processors declare different constructor shapes: some take an IRunSettingsProvider, some take - // an IRunSettingsHelper, some take both, and the rest are parameterless. Pick the matching one. + // Processors declare different constructor shapes: most now take a CommandLineOptions first, + // optionally followed by an IRunSettingsProvider and/or IRunSettingsHelper; a few legacy ones take + // only a run settings dependency, and the rest are parameterless. Pick the matching one. + var commandLineOptions = CommandLineOptions.Instance; var runSettingsProvider = new TestableRunSettingsProvider(); var runSettingsHelper = new RunSettingsHelper(); - var instance = (processor.GetConstructor([typeof(IRunSettingsProvider), typeof(IRunSettingsHelper)]) is { } providerAndHelperCtor - ? providerAndHelperCtor.Invoke([runSettingsProvider, runSettingsHelper]) - : processor.GetConstructor([typeof(IRunSettingsProvider)]) is { } providerCtor - ? providerCtor.Invoke([runSettingsProvider]) - : processor.GetConstructor([typeof(IRunSettingsHelper)]) is { } helperCtor - ? helperCtor.Invoke([runSettingsHelper]) - : Activator.CreateInstance(processor)) as IArgumentProcessor; + var instance = (processor.GetConstructor([typeof(CommandLineOptions), typeof(IRunSettingsProvider), typeof(IRunSettingsHelper)]) is { } optionsProviderHelperCtor + ? optionsProviderHelperCtor.Invoke([commandLineOptions, runSettingsProvider, runSettingsHelper]) + : processor.GetConstructor([typeof(CommandLineOptions), typeof(IRunSettingsProvider)]) is { } optionsProviderCtor + ? optionsProviderCtor.Invoke([commandLineOptions, runSettingsProvider]) + : processor.GetConstructor([typeof(CommandLineOptions), typeof(IRunSettingsHelper)]) is { } optionsHelperCtor + ? optionsHelperCtor.Invoke([commandLineOptions, runSettingsHelper]) + : processor.GetConstructor([typeof(CommandLineOptions)]) is { } optionsCtor + ? optionsCtor.Invoke([commandLineOptions]) + : processor.GetConstructor([typeof(IRunSettingsProvider), typeof(IRunSettingsHelper)]) is { } providerAndHelperCtor + ? providerAndHelperCtor.Invoke([runSettingsProvider, runSettingsHelper]) + : processor.GetConstructor([typeof(IRunSettingsProvider)]) is { } providerCtor + ? providerCtor.Invoke([runSettingsProvider]) + : processor.GetConstructor([typeof(IRunSettingsHelper)]) is { } helperCtor + ? helperCtor.Invoke([runSettingsHelper]) + : Activator.CreateInstance(processor)) as IArgumentProcessor; Assert.IsNotNull(instance, $"Unable to instantiate processor: {processor}"); var specialProcessor = instance.Metadata.Value.IsSpecialCommand; From b7a1884481f21ceaf6356026c8e70c23fc99e3ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Tue, 7 Jul 2026 13:47:08 +0200 Subject: [PATCH 23/87] Remove daily maintenance digest workflow (#16227) No one reads it. Closes #16111. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/daily-qa.lock.yml | 1515 --------------------------- .github/workflows/daily-qa.md | 193 ---- 2 files changed, 1708 deletions(-) delete mode 100644 .github/workflows/daily-qa.lock.yml delete mode 100644 .github/workflows/daily-qa.md diff --git a/.github/workflows/daily-qa.lock.yml b/.github/workflows/daily-qa.lock.yml deleted file mode 100644 index 13f708f2af..0000000000 --- a/.github/workflows/daily-qa.lock.yml +++ /dev/null @@ -1,1515 +0,0 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"8e7b51b921136fc5bcdc846f995d377d15a2e077c3e156b0fea38902c779c595","compiler_version":"v0.72.1","strict":true,"agent_id":"copilot"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"bc56a0cad2f450c562810785ef38649c04db812a","version":"v0.72.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} -# ___ _ _ -# / _ \ | | (_) -# | |_| | __ _ ___ _ __ | |_ _ ___ -# | _ |/ _` |/ _ \ '_ \| __| |/ __| -# | | | | (_| | __/ | | | |_| | (__ -# \_| |_/\__, |\___|_| |_|\__|_|\___| -# __/ | -# _ _ |___/ -# | | | | / _| | -# | | | | ___ _ __ _ __| |_| | _____ ____ -# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| -# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ -# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ -# -# This file was automatically generated by gh-aw (v0.72.1). DO NOT EDIT. -# -# To update this file, edit the corresponding .md file and run: -# gh aw compile -# Not all edits will cause changes to this file. -# -# For more information: https://github.github.com/gh-aw/introduction/overview/ -# -# Daily maintenance digest: builds the repo, runs tests, checks repo health, -# surveys all open PRs, and produces a single actionable digest issue. -# The maintainer's morning briefing — one place, zero fluff. -# -# Resolved workflow manifest: -# Imports: -# - shared/repo-build-setup.md -# -# Secrets used: -# - COPILOT_GITHUB_TOKEN -# - GH_AW_CI_TRIGGER_TOKEN -# - GH_AW_GITHUB_MCP_SERVER_TOKEN -# - GH_AW_GITHUB_TOKEN -# - GITHUB_TOKEN -# -# Custom actions used: -# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 -# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 -# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 -# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 -# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 -# -# Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.25.41 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41 -# - ghcr.io/github/gh-aw-firewall/squid:0.25.41 -# - ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c -# - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 -# - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f - -name: "Daily Maintenance Digest" -"on": - schedule: - - cron: "0 4 * * *" - workflow_dispatch: - inputs: - aw_context: - default: "" - description: Agent caller context (used internally by Agentic Workflows). - required: false - type: string - -permissions: {} - -concurrency: - group: "gh-aw-${{ github.workflow }}" - -run-name: "Daily Maintenance Digest" - -jobs: - activation: - runs-on: ubuntu-slim - permissions: - actions: read - contents: read - outputs: - comment_id: "" - comment_repo: "" - engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} - lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} - model: ${{ steps.generate_aw_info.outputs.model }} - secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} - setup-trace-id: ${{ steps.setup.outputs.trace-id }} - stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} - steps: - - name: Setup Scripts - id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 - with: - destination: ${{ runner.temp }}/gh-aw/actions - job-name: ${{ github.job }} - env: - GH_AW_SETUP_WORKFLOW_NAME: "Daily Maintenance Digest" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-qa.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" - - name: Generate agentic run info - id: generate_aw_info - env: - GH_AW_INFO_ENGINE_ID: "copilot" - GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.40" - GH_AW_INFO_AGENT_VERSION: "1.0.40" - GH_AW_INFO_CLI_VERSION: "v0.72.1" - GH_AW_INFO_WORKFLOW_NAME: "Daily Maintenance Digest" - GH_AW_INFO_EXPERIMENTAL: "false" - GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" - GH_AW_INFO_STAGED: "false" - GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","dotnet"]' - GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.41" - GH_AW_INFO_AWMG_VERSION: "" - GH_AW_INFO_FIREWALL_TYPE: "squid" - GH_AW_COMPILED_STRICT: "true" - GITHUB_MCP_LOCKDOWN_EXPLICIT: "true" - GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} - GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); - await main(core, context); - - name: Validate COPILOT_GITHUB_TOKEN secret - id: validate-secret - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - - name: Checkout .github and .agents folders - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - sparse-checkout: | - .github - .agents - .claude - .codex - .crush - .gemini - .opencode - .pi - sparse-checkout-cone-mode: true - fetch-depth: 1 - - name: Save agent config folders for base branch restoration - env: - GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" - GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" - # poutine:ignore untrusted_checkout_exec - run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" - - name: Check workflow lock file - id: check-lock-file - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_WORKFLOW_FILE: "daily-qa.lock.yml" - GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); - await main(); - - name: Check compile-agentic version - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_COMPILED_VERSION: "v0.72.1" - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); - await main(); - - name: Create prompt with built-in context - env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl - GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} - GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} - GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} - GH_AW_GITHUB_WORKFLOW: ${{ github.workflow }} - GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} - # poutine:ignore untrusted_checkout_exec - run: | - bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" - { - cat << 'GH_AW_PROMPT_667e04d34a2217ad_EOF' - - GH_AW_PROMPT_667e04d34a2217ad_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" - cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" - cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" - cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_667e04d34a2217ad_EOF' - - Tools: add_comment(max:5), create_issue, update_issue, create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_667e04d34a2217ad_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_667e04d34a2217ad_EOF' - - GH_AW_PROMPT_667e04d34a2217ad_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_667e04d34a2217ad_EOF' - - The following GitHub context information is available for this workflow: - {{#if __GH_AW_GITHUB_ACTOR__ }} - - **actor**: __GH_AW_GITHUB_ACTOR__ - {{/if}} - {{#if __GH_AW_GITHUB_REPOSITORY__ }} - - **repository**: __GH_AW_GITHUB_REPOSITORY__ - {{/if}} - {{#if __GH_AW_GITHUB_WORKSPACE__ }} - - **workspace**: __GH_AW_GITHUB_WORKSPACE__ - {{/if}} - {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} - - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ - {{/if}} - {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} - - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ - {{/if}} - {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} - - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ - {{/if}} - {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} - - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ - {{/if}} - {{#if __GH_AW_GITHUB_RUN_ID__ }} - - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ - {{/if}} - - - GH_AW_PROMPT_667e04d34a2217ad_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_667e04d34a2217ad_EOF' - - {{#runtime-import .github/workflows/shared/repo-build-setup.md}} - {{#runtime-import .github/workflows/daily-qa.md}} - GH_AW_PROMPT_667e04d34a2217ad_EOF - } > "$GH_AW_PROMPT" - - name: Interpolate variables and render templates - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_ENGINE_ID: "copilot" - GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} - GH_AW_GITHUB_WORKFLOW: ${{ github.workflow }} - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); - await main(); - - name: Substitute placeholders - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} - GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} - GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} - GH_AW_GITHUB_WORKFLOW: ${{ github.workflow }} - GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} - GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - - const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); - - // Call the substitution function - return await substitutePlaceholders({ - file: process.env.GH_AW_PROMPT, - substitutions: { - GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, - GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, - GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, - GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, - GH_AW_GITHUB_WORKFLOW: process.env.GH_AW_GITHUB_WORKFLOW, - GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, - GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST - } - }); - - name: Validate prompt placeholders - env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - # poutine:ignore untrusted_checkout_exec - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" - - name: Print prompt - env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - # poutine:ignore untrusted_checkout_exec - run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - - name: Upload activation artifact - if: success() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: activation - include-hidden-files: true - path: | - /tmp/gh-aw/aw_info.json - /tmp/gh-aw/aw-prompts/prompt.txt - /tmp/gh-aw/aw-prompts/prompt-template.txt - /tmp/gh-aw/aw-prompts/prompt-import-tree.json - /tmp/gh-aw/github_rate_limits.jsonl - /tmp/gh-aw/base - /tmp/gh-aw/.github/agents - if-no-files-found: ignore - retention-days: 1 - - agent: - needs: activation - runs-on: ubuntu-latest - permissions: read-all - concurrency: - group: "gh-aw-copilot-${{ github.workflow }}" - env: - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - GH_AW_ASSETS_ALLOWED_EXTS: "" - GH_AW_ASSETS_BRANCH: "" - GH_AW_ASSETS_MAX_SIZE_KB: 0 - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - GH_AW_WORKFLOW_ID_SANITIZED: dailyqa - outputs: - agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} - checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} - effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} - has_patch: ${{ steps.collect_output.outputs.has_patch }} - inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} - mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} - model: ${{ needs.activation.outputs.model }} - model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} - output: ${{ steps.collect_output.outputs.output }} - output_types: ${{ steps.collect_output.outputs.output_types }} - setup-trace-id: ${{ steps.setup.outputs.trace-id }} - steps: - - name: Setup Scripts - id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 - with: - destination: ${{ runner.temp }}/gh-aw/actions - job-name: ${{ github.job }} - trace-id: ${{ needs.activation.outputs.setup-trace-id }} - env: - GH_AW_SETUP_WORKFLOW_NAME: "Daily Maintenance Digest" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-qa.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" - - name: Set runtime paths - id: set-runtime-paths - run: | - { - echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" - echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" - echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" - } >> "$GITHUB_OUTPUT" - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - name: Create gh-aw temp directory - run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - - name: Configure gh CLI for GitHub Enterprise - run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" - env: - GH_TOKEN: ${{ github.token }} - - name: Configure Git credentials - env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} - GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" - - name: Checkout PR branch - id: checkout-pr - if: | - github.event.pull_request || github.event.issue.pull_request - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - with: - github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); - await main(); - - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 - env: - GH_HOST: github.com - - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 - - name: Parse integrity filter lists - id: parse-guard-vars - env: - GH_AW_BLOCKED_USERS_VAR: ${{ vars.GH_AW_GITHUB_BLOCKED_USERS || '' }} - GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} - GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} - run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" - - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: activation - path: /tmp/gh-aw - - name: Restore agent config folders from base branch - if: steps.checkout-pr.outcome == 'success' - env: - GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" - GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" - run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" - - name: Restore inline sub-agents from activation artifact - env: - GH_AW_SUB_AGENT_DIR: ".github/agents" - GH_AW_SUB_AGENT_EXT: ".agent.md" - run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" - - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41 ghcr.io/github/gh-aw-firewall/squid:0.25.41 ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f - - name: Generate Safe Outputs Config - run: | - mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" - mkdir -p /tmp/gh-aw/safeoutputs - mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_64271d3380cec2ce_EOF' - {"add_comment":{"hide_older_comments":true,"max":5,"target":"*"},"create_issue":{"max":1,"title_prefix":"Daily Maintenance Digest"},"create_pull_request":{"draft":true,"labels":["automation"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"fallback-to-issue"},"create_report_incomplete_issue":{},"mentions":{"enabled":true},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{},"update_issue":{"allow_body":true,"max":1,"target":"*","title_prefix":"Daily Maintenance Digest"}} - GH_AW_SAFE_OUTPUTS_CONFIG_64271d3380cec2ce_EOF - - name: Generate Safe Outputs Tools - env: - GH_AW_TOOLS_META_JSON: | - { - "description_suffixes": { - "add_comment": " CONSTRAINTS: Maximum 5 comment(s) can be added. Target: *. Supports reply_to_id for discussion threading.", - "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"Daily Maintenance Digest\".", - "create_pull_request": " CONSTRAINTS: Maximum 1 pull request(s) can be created. Labels [\"automation\"] will be automatically added. PRs will be created as drafts.", - "update_issue": " CONSTRAINTS: Maximum 1 issue(s) can be updated. Target: *. The target issue title must start with \"Daily Maintenance Digest\"." - }, - "repo_params": {}, - "dynamic_tools": [] - } - GH_AW_VALIDATION_JSON: | - { - "add_comment": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "item_number": { - "issueOrPRNumber": true - }, - "reply_to_id": { - "type": "string", - "maxLength": 256 - }, - "repo": { - "type": "string", - "maxLength": 256 - } - } - }, - "create_issue": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "labels": { - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 - }, - "parent": { - "issueOrPRNumber": true - }, - "repo": { - "type": "string", - "maxLength": 256 - }, - "temporary_id": { - "type": "string" - }, - "title": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 128 - } - } - }, - "create_pull_request": { - "defaultMax": 1, - "fields": { - "base": { - "type": "string", - "sanitize": true, - "maxLength": 128 - }, - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "branch": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "draft": { - "type": "boolean" - }, - "labels": { - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 - }, - "repo": { - "type": "string", - "maxLength": 256 - }, - "title": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 128 - } - } - }, - "missing_data": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "context": { - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "data_type": { - "type": "string", - "sanitize": true, - "maxLength": 128 - }, - "reason": { - "type": "string", - "sanitize": true, - "maxLength": 256 - } - } - }, - "missing_tool": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 512 - }, - "reason": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "tool": { - "type": "string", - "sanitize": true, - "maxLength": 128 - } - } - }, - "noop": { - "defaultMax": 1, - "fields": { - "message": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - } - } - }, - "report_incomplete": { - "defaultMax": 5, - "fields": { - "details": { - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "reason": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 1024 - } - } - }, - "update_issue": { - "defaultMax": 1, - "fields": { - "assignees": { - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 39 - }, - "body": { - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "issue_number": { - "issueOrPRNumber": true - }, - "labels": { - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 - }, - "milestone": { - "optionalPositiveInteger": true - }, - "operation": { - "type": "string", - "enum": [ - "replace", - "append", - "prepend", - "replace-island" - ] - }, - "repo": { - "type": "string", - "maxLength": 256 - }, - "status": { - "type": "string", - "enum": [ - "open", - "closed" - ] - }, - "title": { - "type": "string", - "sanitize": true, - "maxLength": 128 - } - }, - "customValidation": "requiresOneOf:status,title,body" - } - } - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); - await main(); - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - - - name: Start MCP Gateway - id: start-mcp-gateway - env: - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} - GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - run: | - set -eo pipefail - mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" - - # Export gateway environment variables for MCP config and gateway script - export MCP_GATEWAY_PORT="8080" - export MCP_GATEWAY_DOMAIN="host.docker.internal" - export MCP_GATEWAY_HOST_DOMAIN="localhost" - MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${MCP_GATEWAY_API_KEY}" - export MCP_GATEWAY_API_KEY - export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" - mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" - export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" - export DEBUG="*" - - export GH_AW_ENGINE="copilot" - MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') - MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') - DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.6' - - mkdir -p /home/runner/.copilot - GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_d396b1ee21005b93_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" - { - "mcpServers": { - "github": { - "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.0.3", - "env": { - "GITHUB_HOST": "\${GITHUB_SERVER_URL}", - "GITHUB_LOCKDOWN_MODE": "1", - "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", - "GITHUB_READ_ONLY": "1", - "GITHUB_TOOLSETS": "repos,pull_requests,issues" - }, - "guard-policies": { - "allow-only": { - "approval-labels": ${{ steps.parse-guard-vars.outputs.approval_labels }}, - "blocked-users": ${{ steps.parse-guard-vars.outputs.blocked_users }}, - "min-integrity": "none", - "repos": "all", - "trusted-users": ${{ steps.parse-guard-vars.outputs.trusted_users }} - } - } - }, - "safeoutputs": { - "type": "http", - "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" - }, - "guard-policies": { - "write-sink": { - "accept": [ - "*" - ] - } - } - } - }, - "gateway": { - "port": $MCP_GATEWAY_PORT, - "domain": "${MCP_GATEWAY_DOMAIN}", - "apiKey": "${MCP_GATEWAY_API_KEY}", - "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" - } - } - GH_AW_MCP_CONFIG_d396b1ee21005b93_EOF - - name: Mount MCP servers as CLIs - id: mount-mcp-clis - continue-on-error: true - env: - MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} - MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} - MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); - await main(); - - name: Clean credentials - continue-on-error: true - run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" - - name: Audit pre-agent workspace - id: pre_agent_audit - continue-on-error: true - run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh" - - name: Execute GitHub Copilot CLI - id: agentic_execution - # Copilot CLI tool arguments (sorted): - timeout-minutes: 20 - run: | - set -o pipefail - touch /tmp/gh-aw/agent-step-summary.md - GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) - export GH_AW_NODE_BIN - (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.41/awf-config.schema.json","network":{"allowDomains":["*.vsblob.vsassets.io","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.nuget.org","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","azuresearch-usnc.nuget.org","azuresearch-ussc.nuget.org","builds.dotnet.microsoft.com","ci.dot.net","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","dc.services.visualstudio.com","dist.nuget.org","dot.net","dotnet.microsoft.com","dotnetcli.blob.core.windows.net","github.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","nuget.org","nuget.pkg.github.com","nugetregistryv2prod.blob.core.windows.net","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","oneocsp.microsoft.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","pkgs.dev.azure.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com","www.microsoft.com"]},"apiProxy":{"enabled":true,"models":{"auto":["large"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"]}},"container":{"imageTag":"0.25.41"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log - env: - AWF_REFLECT_ENABLED: 1 - COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_API_KEY: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json - GH_AW_PHASE: agent - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.72.1 - GITHUB_API_URL: ${{ github.api_url }} - GITHUB_AW: true - GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows - GITHUB_HEAD_REF: ${{ github.head_ref }} - GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - GITHUB_REF_NAME: ${{ github.ref_name }} - GITHUB_SERVER_URL: ${{ github.server_url }} - GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md - GITHUB_WORKSPACE: ${{ github.workspace }} - GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com - GIT_AUTHOR_NAME: github-actions[bot] - GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com - GIT_COMMITTER_NAME: github-actions[bot] - XDG_CONFIG_HOME: /home/runner - - name: Detect Copilot errors - id: detect-copilot-errors - if: always() - continue-on-error: true - run: node "${RUNNER_TEMP}/gh-aw/actions/detect_copilot_errors.cjs" - - name: Configure Git credentials - env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} - GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" - - name: Copy Copilot session state files to logs - if: always() - continue-on-error: true - run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" - - name: Stop MCP Gateway - if: always() - continue-on-error: true - env: - MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} - MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} - GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} - run: | - bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" - - name: Redact secrets in logs - if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); - await main(); - env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} - SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} - SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Append agent step summary - if: always() - run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" - - name: Copy Safe Outputs - if: always() - env: - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - run: | - mkdir -p /tmp/gh-aw - cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true - - name: Ingest agent output - id: collect_output - if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_ALLOWED_DOMAINS: "*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" - GH_AW_ALLOWED_GITHUB_REFS: "@nohwnd" - GITHUB_SERVER_URL: ${{ github.server_url }} - GITHUB_API_URL: ${{ github.api_url }} - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); - await main(); - - name: Parse agent logs for step summary - if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); - await main(); - - name: Parse MCP Gateway logs for step summary - if: always() - id: parse-mcp-gateway - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); - await main(); - - name: Print firewall logs - if: always() - continue-on-error: true - env: - AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs - run: | - # Fix permissions on firewall logs/audit dirs so they can be uploaded as artifacts - # AWF runs with sudo, creating files owned by root - sudo chmod -R a+rX /tmp/gh-aw/sandbox/firewall 2>/dev/null || true - # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) - if command -v awf &> /dev/null; then - awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" - else - echo 'AWF binary not installed, skipping firewall log summary' - fi - - name: Parse token usage for step summary - if: always() - continue-on-error: true - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); - await main(); - - name: Print AWF reflect summary - if: always() - continue-on-error: true - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs'); - await main(); - - name: Write agent output placeholder if missing - if: always() - run: | - if [ ! -f /tmp/gh-aw/agent_output.json ]; then - echo '{"items":[]}' > /tmp/gh-aw/agent_output.json - fi - - name: Upload agent artifacts - if: always() - continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: agent - path: | - /tmp/gh-aw/aw-prompts/prompt.txt - /tmp/gh-aw/sandbox/agent/logs/ - /tmp/gh-aw/redacted-urls.log - /tmp/gh-aw/mcp-logs/ - /tmp/gh-aw/proxy-logs/ - !/tmp/gh-aw/proxy-logs/proxy-tls/ - /tmp/gh-aw/agent_usage.json - /tmp/gh-aw/agent-stdio.log - /tmp/gh-aw/pre-agent-audit.txt - /tmp/gh-aw/agent/ - /tmp/gh-aw/github_rate_limits.jsonl - /tmp/gh-aw/safeoutputs.jsonl - /tmp/gh-aw/agent_output.json - /tmp/gh-aw/aw-*.patch - /tmp/gh-aw/aw-*.bundle - /tmp/gh-aw/awf-config.json - /tmp/gh-aw/sandbox/firewall/logs/ - /tmp/gh-aw/sandbox/firewall/audit/ - /tmp/gh-aw/sandbox/firewall/awf-reflect.json - if-no-files-found: ignore - - conclusion: - needs: - - activation - - agent - - detection - - safe_outputs - if: > - always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || - needs.activation.outputs.stale_lock_file_failed == 'true') - runs-on: ubuntu-slim - permissions: - contents: write - discussions: write - issues: write - pull-requests: write - concurrency: - group: "gh-aw-conclusion-daily-qa" - cancel-in-progress: false - outputs: - incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} - noop_message: ${{ steps.noop.outputs.noop_message }} - tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} - total_count: ${{ steps.missing_tool.outputs.total_count }} - steps: - - name: Setup Scripts - id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 - with: - destination: ${{ runner.temp }}/gh-aw/actions - job-name: ${{ github.job }} - trace-id: ${{ needs.activation.outputs.setup-trace-id }} - env: - GH_AW_SETUP_WORKFLOW_NAME: "Daily Maintenance Digest" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-qa.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" - - name: Download agent output artifact - id: download-agent-output - continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: agent - path: /tmp/gh-aw/ - - name: Setup agent output environment variable - id: setup-agent-output-env - if: steps.download-agent-output.outcome == 'success' - run: | - mkdir -p /tmp/gh-aw/ - find "/tmp/gh-aw/" -type f -print - echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - - name: Process no-op messages - id: noop - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_NOOP_MAX: "1" - GH_AW_WORKFLOW_NAME: "Daily Maintenance Digest" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_NOOP_REPORT_AS_ISSUE: "false" - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); - await main(); - - name: Log detection run - id: detection_runs - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Daily Maintenance Digest" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} - GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs'); - await main(); - - name: Record missing tool - id: missing_tool - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" - GH_AW_WORKFLOW_NAME: "Daily Maintenance Digest" - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); - await main(); - - name: Record incomplete - id: report_incomplete - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" - GH_AW_WORKFLOW_NAME: "Daily Maintenance Digest" - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); - await main(); - - name: Handle agent failure - id: handle_agent_failure - if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Daily Maintenance Digest" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_WORKFLOW_ID: "daily-qa" - GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" - GH_AW_ENGINE_ID: "copilot" - GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} - GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} - GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} - GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} - GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} - GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} - GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" - GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }} - GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }} - GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} - GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} - GH_AW_GROUP_REPORTS: "false" - GH_AW_FAILURE_REPORT_AS_ISSUE: "true" - GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" - GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" - GH_AW_TIMEOUT_MINUTES: "20" - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); - await main(); - - detection: - needs: - - activation - - agent - if: > - always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') - runs-on: ubuntu-latest - permissions: - contents: read - outputs: - detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} - detection_reason: ${{ steps.detection_conclusion.outputs.reason }} - detection_success: ${{ steps.detection_conclusion.outputs.success }} - steps: - - name: Setup Scripts - id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 - with: - destination: ${{ runner.temp }}/gh-aw/actions - job-name: ${{ github.job }} - trace-id: ${{ needs.activation.outputs.setup-trace-id }} - env: - GH_AW_SETUP_WORKFLOW_NAME: "Daily Maintenance Digest" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-qa.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" - - name: Download agent output artifact - id: download-agent-output - continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: agent - path: /tmp/gh-aw/ - - name: Setup agent output environment variable - id: setup-agent-output-env - if: steps.download-agent-output.outcome == 'success' - run: | - mkdir -p /tmp/gh-aw/ - find "/tmp/gh-aw/" -type f -print - echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - - name: Checkout repository for patch context - if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - # --- Threat Detection --- - - name: Clean stale firewall files from agent artifact - run: | - rm -rf /tmp/gh-aw/sandbox/firewall/logs - rm -rf /tmp/gh-aw/sandbox/firewall/audit - - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41 ghcr.io/github/gh-aw-firewall/squid:0.25.41 - - name: Check if detection needed - id: detection_guard - if: always() - env: - OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }} - HAS_PATCH: ${{ needs.agent.outputs.has_patch }} - run: | - if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then - echo "run_detection=true" >> "$GITHUB_OUTPUT" - echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" - else - echo "run_detection=false" >> "$GITHUB_OUTPUT" - echo "Detection skipped: no agent outputs or patches to analyze" - fi - - name: Clear MCP Config for detection - if: always() && steps.detection_guard.outputs.run_detection == 'true' - run: | - rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" - rm -f /home/runner/.copilot/mcp-config.json - rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" - - name: Prepare threat detection files - if: always() && steps.detection_guard.outputs.run_detection == 'true' - run: | - mkdir -p /tmp/gh-aw/threat-detection/aw-prompts - cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true - cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true - for f in /tmp/gh-aw/aw-*.patch; do - [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true - done - for f in /tmp/gh-aw/aw-*.bundle; do - [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true - done - echo "Prepared threat detection files:" - ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true - - name: Setup threat detection - if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - WORKFLOW_NAME: "Daily Maintenance Digest" - WORKFLOW_DESCRIPTION: "Daily maintenance digest: builds the repo, runs tests, checks repo health,\nsurveys all open PRs, and produces a single actionable digest issue.\nThe maintainer's morning briefing — one place, zero fluff." - HAS_PATCH: ${{ needs.agent.outputs.has_patch }} - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs'); - await main(); - - name: Ensure threat-detection directory and log - if: always() && steps.detection_guard.outputs.run_detection == 'true' - run: | - mkdir -p /tmp/gh-aw/threat-detection - touch /tmp/gh-aw/threat-detection/detection.log - - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: '24' - package-manager-cache: false - - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 - env: - GH_HOST: github.com - - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 - - name: Execute GitHub Copilot CLI - if: always() && steps.detection_guard.outputs.run_detection == 'true' - continue-on-error: true - id: detection_agentic_execution - # Copilot CLI tool arguments (sorted): - timeout-minutes: 20 - run: | - set -o pipefail - touch /tmp/gh-aw/agent-step-summary.md - GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) - export GH_AW_NODE_BIN - (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.41/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true},"container":{"imageTag":"0.25.41"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log - env: - AWF_REFLECT_ENABLED: 1 - COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_API_KEY: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_PHASE: detection - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.72.1 - GITHUB_API_URL: ${{ github.api_url }} - GITHUB_AW: true - GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows - GITHUB_HEAD_REF: ${{ github.head_ref }} - GITHUB_REF_NAME: ${{ github.ref_name }} - GITHUB_SERVER_URL: ${{ github.server_url }} - GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md - GITHUB_WORKSPACE: ${{ github.workspace }} - GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com - GIT_AUTHOR_NAME: github-actions[bot] - GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com - GIT_COMMITTER_NAME: github-actions[bot] - XDG_CONFIG_HOME: /home/runner - - name: Upload threat detection log - if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: detection - path: /tmp/gh-aw/threat-detection/detection.log - if-no-files-found: ignore - - name: Parse and conclude threat detection - id: detection_conclusion - if: always() - continue-on-error: true - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} - GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" - with: - script: | - try { - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); - await main(); - } catch (loadErr) { - const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; - const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); - core.error(msg); - core.setOutput('reason', 'parse_error'); - if (continueOnError) { - core.warning('\u26A0\uFE0F ' + msg); - core.setOutput('conclusion', 'warning'); - core.setOutput('success', 'false'); - } else { - core.setOutput('conclusion', 'failure'); - core.setOutput('success', 'false'); - core.setFailed(msg); - } - } - - safe_outputs: - needs: - - activation - - agent - - detection - if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' - runs-on: ubuntu-slim - permissions: - contents: write - discussions: write - issues: write - pull-requests: write - timeout-minutes: 15 - env: - GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/daily-qa" - GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} - GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} - GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} - GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.40" - GH_AW_WORKFLOW_ID: "daily-qa" - GH_AW_WORKFLOW_NAME: "Daily Maintenance Digest" - outputs: - code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} - code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} - comment_id: ${{ steps.process_safe_outputs.outputs.comment_id }} - comment_url: ${{ steps.process_safe_outputs.outputs.comment_url }} - create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} - create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} - created_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }} - created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_url }} - created_pr_number: ${{ steps.process_safe_outputs.outputs.created_pr_number }} - created_pr_url: ${{ steps.process_safe_outputs.outputs.created_pr_url }} - process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} - process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} - steps: - - name: Setup Scripts - id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 - with: - destination: ${{ runner.temp }}/gh-aw/actions - job-name: ${{ github.job }} - trace-id: ${{ needs.activation.outputs.setup-trace-id }} - env: - GH_AW_SETUP_WORKFLOW_NAME: "Daily Maintenance Digest" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-qa.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" - - name: Download agent output artifact - id: download-agent-output - continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: agent - path: /tmp/gh-aw/ - - name: Setup agent output environment variable - id: setup-agent-output-env - if: steps.download-agent-output.outcome == 'success' - run: | - mkdir -p /tmp/gh-aw/ - find "/tmp/gh-aw/" -type f -print - echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - - name: Download patch artifact - continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: agent - path: /tmp/gh-aw/ - - name: Extract base branch from agent output - id: extract-base-branch - if: steps.download-agent-output.outcome == 'success' - shell: bash - run: | - if [ -f "/tmp/gh-aw/agent_output.json" ]; then - GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - BASE_BRANCH=$("$GH_AW_NODE" -e " - try { - const data = JSON.parse(require('fs').readFileSync('/tmp/gh-aw/agent_output.json', 'utf8')); - const item = (data.items || []).find(i => - (i.type === 'create_pull_request' || i.type === 'push_to_pull_request_branch') && - i.base_branch - ); - if (item) process.stdout.write(item.base_branch); - } catch(e) {} - " 2>/dev/null || true) - # Validate: only allow safe git branch name characters - if [[ "$BASE_BRANCH" =~ ^[a-zA-Z0-9/_.-]+$ ]] && [ ${#BASE_BRANCH} -le 255 ]; then - printf 'base-branch=%s\n' "$BASE_BRANCH" >> "$GITHUB_OUTPUT" - echo "Extracted base branch from safe output: $BASE_BRANCH" - fi - fi - - name: Checkout repository - if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - ref: ${{ steps.extract-base-branch.outputs.base-branch || github.base_ref || github.event.pull_request.base.ref || github.ref_name || github.event.repository.default_branch }} - token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - persist-credentials: false - fetch-depth: 1 - - name: Configure Git credentials - if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') - env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} - GIT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GIT_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" - - name: Configure GH_HOST for enterprise compatibility - id: ghes-host-config - shell: bash - run: | - # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct - # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. - GH_HOST="${GITHUB_SERVER_URL#https://}" - GH_HOST="${GH_HOST#http://}" - echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" - - name: Process Safe Outputs - id: process_safe_outputs - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_ALLOWED_DOMAINS: "*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" - GITHUB_SERVER_URL: ${{ github.server_url }} - GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":5,\"target\":\"*\"},\"create_issue\":{\"max\":1,\"title_prefix\":\"Daily Maintenance Digest\"},\"create_pull_request\":{\"draft\":true,\"labels\":[\"automation\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"fallback-to-issue\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{},\"update_issue\":{\"allow_body\":true,\"max\":1,\"target\":\"*\",\"title_prefix\":\"Daily Maintenance Digest\"}}" - GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); - await main(); - - name: Upload Safe Outputs Items - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: safe-outputs-items - path: | - /tmp/gh-aw/safe-output-items.jsonl - /tmp/gh-aw/temporary-id-map.json - if-no-files-found: ignore - diff --git a/.github/workflows/daily-qa.md b/.github/workflows/daily-qa.md deleted file mode 100644 index 46f9ea1df2..0000000000 --- a/.github/workflows/daily-qa.md +++ /dev/null @@ -1,193 +0,0 @@ ---- -description: | - Daily maintenance digest: builds the repo, runs tests, checks repo health, - surveys all open PRs, and produces a single actionable digest issue. - The maintainer's morning briefing — one place, zero fluff. - -on: - schedule: "0 4 * * *" - workflow_dispatch: - -timeout-minutes: 20 - -permissions: read-all - -network: - allowed: - - defaults - - dotnet - -imports: - - shared/repo-build-setup.md - -safe-outputs: - noop: - report-as-issue: false - mentions: true - allowed-github-references: ["@nohwnd"] - create-issue: - max: 1 - title-prefix: "Daily Maintenance Digest" - labels: [] - update-issue: - max: 1 - target: "*" - title-prefix: "Daily Maintenance Digest" - add-comment: - target: "*" - max: 5 - hide-older-comments: true - create-pull-request: - draft: true - labels: [automation] - protected-files: fallback-to-issue - -tools: - github: - lockdown: true - toolsets: [repos, pull_requests, issues] - min-integrity: none - bash: true - edit: ---- - -# Daily Maintenance Digest - -Your name is ${{ github.workflow }}. You are the daily maintenance agent for `${{ github.repository }}`. You produce a **single digest issue** each day covering repo health and PR status. This is the maintainer's morning briefing — concise, actionable, one place. - -This repository has **one primary maintainer** (`@nohwnd`) who can approve and merge. Your job is to save them time. **Always mention `@nohwnd`** at the top of the digest so they get an email notification. - -## Anti-Noise Rules - -- **One digest issue, updated daily.** Never create a second one. -- **Never comment on PRs just to say "still waiting."** Only comment when there's a new actionable item (conflicts appeared, CI broke). -- **Never comment on a PR if a human maintainer commented in the last 48 hours** — they're handling it. -- If everything is green and there are no PRs, report noop and exit. - -## Security Concerns Are Out of Scope - -This workflow does not assess, discuss, or make recommendations about potential security implications of issues or PRs. If an issue or PR claims to describe a security vulnerability, do not evaluate whether the claim is valid, do not discuss the potential impact, and do not include any security analysis in the digest or in any comment. Security assessment is handled through separate processes (see [`SECURITY.md`](../../SECURITY.md)). - -## Process - -### Part 1: Repo Health - -1. **Build check**: Run `./build.sh` and note whether it passes or fails. -2. **Test check**: Run `./test.sh` and note pass/fail counts. -3. **vstest-specific checks** (scan files, don't need to build again): - - Binding redirects: Check that `vstest.console/app.config`, `testhost.x86/app.config`, and `datacollector/app.config` have consistent binding redirect entries - - Package verification: Check that `eng/expected-nupkg-file-counts.json` and `eng/expected-dll-frameworks.json` look reasonable - - PublicAPI files: Check for `PublicAPI.Unshipped.txt` entries that should have been shipped - - Stale xlf files: Check if any `.xlf` files are out of sync with their `.resx` files -4. **Quick fixes**: If you find small problems you can fix with very high confidence (typos in docs, dead imports), create a draft PR. -5. **Issues for real problems**: For each distinct problem, check if a duplicate issue exists first. If not, create one with clear repro steps. - -### Part 2: PR Survey - -6. Fetch all open PRs: - -```bash -gh pr list --repo ${{ github.repository }} \ - --state open \ - --json number,title,author,createdAt,updatedAt,mergeable,labels,reviewDecision,statusCheckRollup,isDraft,headRefName,url \ - --limit 50 \ - > /tmp/open-prs.json -``` - -7. Classify each PR: - -| Status | Criteria | Recommended action | -|---|---|---| -| 🟢 Ready to merge | All checks pass, no conflicts, has approval or is bot PR | `MERGE` or `CLOSE` if superseded | -| 🔴 Needs maintainer | Checks pass but no review, or author addressed comments | `REVIEW` or `INVESTIGATE` | -| 🟡 Waiting on author | Merge conflicts, CI failing from code issues, or review changes requested | `WAITING ON AUTHOR` | -| ⚪ In progress | Draft, CI running, or pushed < 1 hour ago | `NO ACTION` | - -8. **Nudge authors** (only when needed): Comment on a PR **only if**: - - You haven't already commented about this specific issue - - A human maintainer hasn't commented in the last 48 hours - - The condition has persisted for >2 days - -### Part 3: Write the Digest - -9. Search for an existing open issue whose title starts with **"Daily Maintenance Digest"**. If one exists, update its body. If not, create one. - -**Digest format:** - -```markdown -## Repo Health — YYYY-MM-DD - -- **Build**: ✅ passing / ❌ failing (brief reason) -- **Tests**: ✅ N passed / ❌ N failed (list failures if any) -- **Issues found**: N new issues created, N existing issues updated -- **Auto-fix PRs**: N draft PRs created - -
vstest-specific checks - -- Binding redirects: ✅ consistent / ⚠️ [details] -- Package verification: ✅ / ⚠️ [details] -- PublicAPI: ✅ / ⚠️ [details] -- xlf sync: ✅ / ⚠️ [details] - -
- -## PR Status — YYYY-MM-DD - -### 🟢 Ready to merge (N) -| PR | Title | Author | Action | -|---|---|---|---| -| #123 | Fix binding redirect | @user | **Merge** — all green | - -### 🔴 Needs your review (N) -| PR | Title | Author | Waiting since | Action | -|---|---|---|---|---| -| #125 | Add new logger | @contributor | 3 days | **Review** — checks pass | - -### 🟡 Waiting on author (N) -| PR | Title | Author | Issue | Action | -|---|---|---|---|---| -| #126 | Update API | @contributor | Merge conflicts | Commented, waiting for rebase | - -### ⚪ In progress (N) -| PR | Title | Author | Status | -|---|---|---|---| -| #128 | [WIP] Refactor engine | @contributor | Draft | - -## Summary -- **Your action needed**: N PRs to review/merge -- **Waiting on authors**: N PRs -- **Repo health**: ✅ green / ⚠️ issues found - -## Issues Backlog — YYYY-MM-DD - -To get label counts, list all open issues and count them by label. Do not write "unknown" or "unavailable" — actually paginate through the issues and count. If the repo has many issues, use search queries filtered by label (e.g. search for `is:issue is:open label:"Needs: Triage :mag:"`). - -| Category | Count | -|---|---| -| Total open issues | N | -| Untriaged (`Needs: Triage :mag:`) | N | -| Waiting for info (`Needs: Additional Info`) | N | -| Blocked / Design needed | N | -| Actionable bugs (has repro, no linked PR) | N | -| Agent-created fix PRs in flight | N | - -**Issue Triage Progress**: The Issue Repro Triage agent runs daily and picks one issue to investigate. Check for recent `issue-repro-triage` workflow runs and report what it worked on. If it created a draft fix PR, mention it here. - -**Trend**: ↓ N issues closed this week / ↑ N new issues opened - -cc @nohwnd -``` - -If there are zero open PRs and everything is green: - -```json -{"noop": {"message": "Repo healthy, no open PRs. Nothing to report."}} -``` - -## Important Notes - -- This is primarily a **reporting** workflow. It does not merge, close, or rebase any PRs. -- The one exception: it may comment on PRs to nudge authors about conflicts or CI failures. -- It may create draft PRs for trivial fixes (typos, dead code). -- Keep the digest scannable — the maintainer should know what to do in 30 seconds. -- **Tables must be valid GitHub Markdown.** Every table row must have the same number of `|` separators as the header row. Always include the `|---|` separator row. Verify column counts before outputting. From 77c3673ef28372023e5602802748ab727408a33f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Tue, 7 Jul 2026 14:25:41 +0200 Subject: [PATCH 24/87] Prefer org tokens over personal PATs in agentic workflows (#16226) The Microsoft OSS enterprise now 403s fine-grained PATs older than 8 days, so the personal-PAT setup breaks on a short cycle and takes every agentic workflow down with it at once. Move auth onto org-owned credentials, mirroring testfx: - Copilot inference now uses the copilot-requests: write permission (billed to the org Copilot subscription) instead of COPILOT_GITHUB_TOKEN. No compiled lock references that secret anymore. read-all workflows expand to an explicit read map so the one write scope can be added. - Drop lockdown: true from the issue/PR workflows (deprecated upstream; they keep min-integrity: none), so MCP reads fall back to the default GITHUB_TOKEN. - The two write-back workflows (issue-repro-triage, pr-iteration) reference an org-owned GitHub App under safe-outputs with ignore-if-missing, so they fall back to GITHUB_TOKEN until an admin provisions APP_ID/APP_PRIVATE_KEY. - Bump gh-aw 0.72.1 -> 0.81.6 and recompile every lock file. Adds .github/workflows/README.md documenting the token model and updates the AGENTS.md secrets note. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/aw/actions-lock.json | 17 +- .github/workflows/README.md | 229 +++++ .github/workflows/agentic_commands.yml | 13 +- .github/workflows/agentics-maintenance.yml | 90 +- .../build-failure-analysis-command.lock.yml | 419 +++++---- .../build-failure-analysis-command.md | 1 + .../workflows/build-failure-analysis.lock.yml | 426 +++++---- .github/workflows/build-failure-analysis.md | 1 + .github/workflows/code-simplifier.lock.yml | 479 ++++++----- .github/workflows/code-simplifier.md | 18 +- .github/workflows/daily-file-diet.lock.yml | 416 +++++---- .github/workflows/daily-file-diet.md | 1 + .../workflows/efficiency-improver.lock.yml | 512 ++++++----- .github/workflows/efficiency-improver.md | 18 +- .github/workflows/http-link-checker.lock.yml | 595 ++++++++----- .github/workflows/http-link-checker.md | 18 +- .github/workflows/issue-repro-triage.lock.yml | 801 +++++++++++------ .github/workflows/issue-repro-triage.md | 11 +- .../workflows/malicious-code-scan.lock.yml | 363 ++++---- .github/workflows/malicious-code-scan.md | 1 + .github/workflows/markdown-linter.lock.yml | 422 +++++---- .github/workflows/markdown-linter.md | 1 + .github/workflows/md-link-checker.lock.yml | 485 ++++++----- .github/workflows/md-link-checker.md | 18 +- .../workflows/msbuild-quality-review.lock.yml | 457 +++++----- .github/workflows/msbuild-quality-review.md | 1 + .github/workflows/pr-expert-reviewer.lock.yml | 700 ++++++++++----- .github/workflows/pr-expert-reviewer.md | 2 +- .github/workflows/pr-iteration.lock.yml | 806 ++++++++++++------ .github/workflows/pr-iteration.md | 11 +- .../repository-quality-improver.lock.yml | 422 +++++---- .../workflows/repository-quality-improver.md | 1 + AGENTS.md | 3 +- 33 files changed, 4766 insertions(+), 2992 deletions(-) create mode 100644 .github/workflows/README.md diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index f129955ada..d64e7748fa 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -25,20 +25,15 @@ "version": "v4", "sha": "ea165f8d65b6e75b540449e92b4886f43607fa02" }, - "github/gh-aw-actions/setup-cli@v0.79.8": { + "github/gh-aw-actions/setup-cli@v0.81.6": { "repo": "github/gh-aw-actions/setup-cli", - "version": "v0.79.8", - "sha": "c0338fef4749d08c21f8f975fb0e37efa17dda47" + "version": "v0.81.6", + "sha": "ba6380cc6e5be5d21677bebe04d52fb48e3abec7" }, - "github/gh-aw-actions/setup@v0.79.8": { + "github/gh-aw-actions/setup@v0.81.6": { "repo": "github/gh-aw-actions/setup", - "version": "v0.79.8", - "sha": "c0338fef4749d08c21f8f975fb0e37efa17dda47" - }, - "github/gh-aw/actions/setup-cli@v0.79.8": { - "repo": "github/gh-aw/actions/setup-cli", - "version": "v0.79.8", - "sha": "8b02ab336d100a5746e9f53b8bc2b22878278a6f" + "version": "v0.81.6", + "sha": "ba6380cc6e5be5d21677bebe04d52fb48e3abec7" }, "super-linter/super-linter@v8.6.0": { "repo": "super-linter/super-linter", diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000000..60eb49f4f9 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,229 @@ +# `.github/workflows/` + +This directory hosts every GitHub Actions workflow that runs in `microsoft/vstest`, +including the AI-powered agentic workflows generated with the [`gh aw`][gh-aw] CLI. + +Two file styles coexist: + +- **Regular workflows** — plain `*.yml` files authored by hand and consumed directly by + GitHub Actions. +- **Agentic workflows** — `*.md` sources compiled to companion `*.lock.yml` files via + `gh aw compile`. The `.md` file is the source of truth; the `.lock.yml` is generated + and must be regenerated whenever the source changes. + +Reusable building blocks for agentic workflows live under [`shared/`](./shared) and are +imported through the `imports:` frontmatter field. + +## Working on agentic workflows + +> [!IMPORTANT] +> Never hand-edit `*.lock.yml`. They are regenerated by `gh aw compile`. + +```bash +# Install the gh-aw CLI extension (once per machine) +gh extension install github/gh-aw + +# Compile a single workflow after editing its .md source. Strict mode is the +# default — keep it that way. NEVER set `strict: false` in frontmatter. +gh aw compile + +# Force strict-mode validation across all workflows +gh aw compile --strict + +# Trigger a workflow on demand +gh aw run + +# Inspect or debug a recent run +gh aw logs +gh aw audit +``` + +For deeper guidance — creating, updating, debugging, upgrading, or wrapping MCP servers — +see the dispatcher [`.github/agents/agentic-workflows.agent.md`](../agents/agentic-workflows.agent.md). + +## Secrets & authentication + +The goal for this repository is to run every agentic workflow with **no long-lived +personal PAT**. Authentication flows through org-owned credentials instead: + +| Secret / variable / permission | Used for | Notes | +| --- | --- | --- | +| `copilot-requests: write` (permission) | GitHub Copilot CLI (model inference) | **Preferred.** Declared in every agentic workflow's `permissions:` block; gh-aw then authenticates inference with the per-run `GITHUB_TOKEN` and bills through the org's Copilot subscription. Replaces `COPILOT_GITHUB_TOKEN`. | +| `COPILOT_GITHUB_TOKEN` (secret) | *(legacy)* Copilot inference | **No longer referenced** by any compiled workflow — `copilot-requests: write` supersedes it. Safe to delete after this change ships. | +| `APP_ID` (variable) + `APP_PRIVATE_KEY` (secret) | Org-owned GitHub App for safe-output write-backs | **Preferred write path.** Mints a short-lived token per run, scoped to the job's `permissions:`, auto-revoked when the run ends — and, unlike `GITHUB_TOKEN`, it **triggers downstream CI**. Wired with `ignore-if-missing: true`, so workflows fall back gracefully until an org admin provisions these. | +| `GH_AW_GITHUB_TOKEN` (secret) | *(legacy)* safe-output writes / MCP reads needing more than `GITHUB_TOKEN` | **Optional.** Falls back to `GITHUB_TOKEN` when unset. Retire once the GitHub App is provisioned. | +| `GH_AW_GITHUB_MCP_SERVER_TOKEN` (secret) | GitHub MCP reads (head of the token chain) | **Optional.** Chain: `GH_AW_GITHUB_MCP_SERVER_TOKEN \|\| GH_AW_GITHUB_TOKEN \|\| GITHUB_TOKEN`. | +| `GH_AW_CI_TRIGGER_TOKEN` (secret) | Trigger CI after a write-back push | **Optional.** The GitHub App token already triggers downstream CI, so this can be retired once the App is in place. | +| `GITHUB_TOKEN` (built-in) | Default per-run auth | Always present; scoped to the job's `permissions:` and auto-revoked at run end. | + +> [!IMPORTANT] +> **Fine-grained PATs expire, and the `Microsoft Open Source` enterprise now hard-rejects +> any fine-grained PAT whose lifetime exceeds 8 days** (the API returns `403`). A PAT-based +> setup therefore breaks on a short cycle: when the token lapses — or simply outlives the +> 8-day window — every workflow that depends on it fails at once and files a burst of +> `[aw] … failed` issues. The two PAT-free options below let this repo run agentic +> workflows with **no long-lived PAT at all**. +> +> **Fast unblock:** delete `COPILOT_GITHUB_TOKEN` (inference now uses `copilot-requests: +> write`) and, if present, `GH_AW_GITHUB_TOKEN`. Because no source workflow forces a custom +> PAT anymore (`lockdown` was removed repo-wide and all declare `min-integrity: none`), +> every workflow degrades gracefully to the built-in `GITHUB_TOKEN`. The only case that +> still needs elevated auth is a write-back on a **fork** PR (where `GITHUB_TOKEN` is +> read-only) — use the GitHub App below for those. + +### Preferred: eliminate the expiring PATs + +**1. Replace `COPILOT_GITHUB_TOKEN` with `copilot-requests: write`.** +When a workflow's `permissions:` block grants `copilot-requests: write`, gh-aw +authenticates Copilot inference with the per-run `GITHUB_TOKEN` and bills through the org's +Copilot subscription — no PAT, no secret to rotate. Every agentic workflow in this repo +already declares it: + +```yaml +permissions: + contents: read + copilot-requests: write +``` + +> [!NOTE] +> `permissions: read-all` cannot be combined with a single `write` scope, so workflows that +> previously used `read-all` now declare an explicit read map plus `copilot-requests: +> write` (equivalent read surface, plus the one write the compiler needs). + +**2. Replace `GH_AW_GITHUB_TOKEN` with an org-owned GitHub App.** +A GitHub App mints a **short-lived token at the start of each run, scoped to the job's +`permissions:`, and automatically revoked when the run ends** — satisfying the org's +short-PAT policy without manual rotation. Only `COPILOT_GITHUB_TOKEN` cannot use an App +(covered by option 1 instead). + +Set it up once (requires org admin to create/install the App): + +1. Create a GitHub App owned by the `microsoft` org (Settings → Developer settings → + GitHub Apps). Grant the read/write repository permissions the workflows need (Contents, + Issues, Pull requests), generate a **private key** (`.pem`), and **install** the App on + `microsoft/vstest`. +2. Store the App ID as a repository **variable** and the private key as a **secret**: + + ```bash + gh variable set APP_ID --repo microsoft/vstest --body "" + gh secret set APP_PRIVATE_KEY --repo microsoft/vstest --body "$(cat path/to/private-key.pem)" + ``` + +3. The two write-back workflows already reference the App under `safe-outputs.github-app` + in their source `.md` (with `ignore-if-missing: true` for fork-PR fallback): + + ```yaml + safe-outputs: + github-app: + client-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + ignore-if-missing: true + ``` + + Once `APP_ID`/`APP_PRIVATE_KEY` exist, the compiled lock uses + [`actions/create-github-app-token@v3.2.0`][app-token] to mint the token; until then the + step is skipped and write-backs fall back to `GH_AW_GITHUB_TOKEN || GITHUB_TOKEN`. + +See the upstream reference: . + +### Lockdown mode has been removed from this repo's workflows + +Historically the workflows that read issues/PRs set `lockdown: true` on the GitHub MCP +tool. Lockdown mode **rejected the default `GITHUB_TOKEN`** and forced a custom PAT +(`GH_AW_GITHUB_MCP_SERVER_TOKEN || GH_AW_GITHUB_TOKEN || GITHUB_TOKEN`), so a single +missing/expired PAT failed *all* of them at activation. + +`lockdown:` is **deprecated** upstream in favour of +[integrity filtering](https://github.com/github/gh-aw/blob/main/docs/src/content/docs/reference/integrity.md) +(`min-integrity`). These workflows already declared `min-integrity: none` (they +intentionally examine any issue/PR), so `lockdown: true` only added the PAT requirement. +It has been dropped; the content-filtering behaviour is unchanged, and the default +`GITHUB_TOKEN` now suffices unless a workflow needs elevated access (then use the GitHub +App above). + +> [!NOTE] +> Not every `[aw] … failed` issue is a token problem. The failure banner usually names the +> cause — *AI credits budget exceeded*, an engine/inference error, or transient +> container-image / AWF-binary download failures are all unrelated to authentication. + +## Catalog + +### Agentic workflows + +#### Code review + +| Workflow | Trigger | Description | +| --- | --- | --- | +| [`pr-expert-reviewer.md`](./pr-expert-reviewer.md) | PR opened/synchronize/reopened/ready_for_review + manual | Runs the `expert-reviewer` agent on pull requests. | +| [`msbuild-quality-review.md`](./msbuild-quality-review.md) | Weekly + manual | Reviews `.props`, `.targets`, `Directory.Build.*`, `Directory.Packages.props`, and NuGet `build*/` extensions for authoring anti-patterns via the `msbuild-reviewer` agent. | + +#### Build & test diagnostics + +| Workflow | Trigger | Description | +| --- | --- | --- | +| [`build-failure-analysis.md`](./build-failure-analysis.md) | PR opened/synchronize/reopened on `main` or `rel/*` + manual | Runs the build; on failure the `build-failure-analyst` agent posts a summary comment and inline `suggestion` blocks. Advisory only — not a gating check. | +| [`build-failure-analysis-command.md`](./build-failure-analysis-command.md) | `/analyze-build-failure` on a PR | Re-runs the build-failure analysis on demand (after force-pushes, dismissed comments, etc.). | + +#### Issue & PR automation + +| Workflow | Trigger | Description | +| --- | --- | --- | +| [`issue-repro-triage.md`](./issue-repro-triage.md) | Every 12h + manual + issues | Reproduces reported issues and opens auto-fix PRs. | +| [`pr-iteration.md`](./pr-iteration.md) | PR review submitted + issue comment + daily + manual | Addresses PR review feedback and pushes fixes to the PR branch. | + +#### Continuous quality improvers (scheduled) + +| Workflow | Trigger | Description | +| --- | --- | --- | +| [`code-simplifier.md`](./code-simplifier.md) | Daily + issues | Analyzes recently modified code and opens PRs that simplify it while preserving behavior. | +| [`efficiency-improver.md`](./efficiency-improver.md) | Daily + manual + issues | Identifies and implements energy/compute efficiency improvements (capped at 8 open PRs). | +| [`repository-quality-improver.md`](./repository-quality-improver.md) | Weekdays + manual | Rotating repository-quality analysis; opens tracking issues. | +| [`daily-file-diet.md`](./daily-file-diet.md) | Weekdays + manual + issues | Identifies oversized source files and opens actionable refactoring issues. | +| [`daily-qa.md`](./daily-qa.md) | Daily (04:00 UTC) + manual + issues | Daily maintenance digest — ad hoc, subjective quality assurance. | +| [`malicious-code-scan.md`](./malicious-code-scan.md) | Daily + manual | Reviews recent code changes for suspicious patterns indicating malicious or agentic threats. | + +#### Docs & links + +| Workflow | Trigger | Description | +| --- | --- | --- | +| [`markdown-linter.md`](./markdown-linter.md) | Weekdays (14:00 UTC) + manual + issues | Runs Markdown quality checks and opens issues for violations. | +| [`md-link-checker.md`](./md-link-checker.md) | Weekly (Friday) | Finds and fixes broken relative links in documentation. | +| [`http-link-checker.md`](./http-link-checker.md) | Weekly (Friday) | Finds and fixes broken HTTP links in documentation. | + +### Regular workflows + +| Workflow | Trigger | Description | +| --- | --- | --- | +| [`agentic_commands.yml`](./agentic_commands.yml) | PR / issue comments | Dispatches `/`-prefixed slash commands typed in comments to the right agentic workflow. | +| [`agentics-maintenance.yml`](./agentics-maintenance.yml) | Schedule + manual | Maintains the agentic workflow ecosystem itself (re-compilation, dependency bumps, etc.). | +| [`copilot-setup-steps.yml`](./copilot-setup-steps.yml) | PR + push + manual | Bootstraps a Copilot Coding Agent environment with the right .NET SDK and tooling. | +| [`enable-auto-merge.yml`](./enable-auto-merge.yml) | `pull_request_target` | Enables auto-merge on eligible PRs. | + +## Shared components + +Reusable agentic-workflow snippets imported via `imports:` in workflow frontmatter: + +| Component | Used by | +| --- | --- | +| [`shared/build-failure-analysis-shared.md`](./shared/build-failure-analysis-shared.md) | `build-failure-analysis.md`, `build-failure-analysis-command.md` | +| [`shared/msbuild-review-shared.md`](./shared/msbuild-review-shared.md) | `msbuild-quality-review.md` | +| [`shared/repo-build-setup.md`](./shared/repo-build-setup.md) | Workflows that restore + build the repo before the agent runs | +| [`shared/formatting.md`](./shared/formatting.md) | Quality improver workflows (output formatting conventions) | +| [`shared/reporting.md`](./shared/reporting.md) | Quality improver workflows (issue/PR body templates) | + +## Conventions + +- **Strict mode is mandatory.** Workflow frontmatter must not set `strict: false`. When in + doubt, run `gh aw compile --strict`. +- **Source of truth.** Edit the `.md` file (and any imported `shared/*.md`); never the + `.lock.yml`. +- **One change, one compile.** After editing an agentic workflow source, run + `gh aw compile ` and commit the regenerated `.lock.yml` in the same change. +- **Pinned actions only.** Strict mode pins every `uses:` reference to a SHA; the compiler + enforces this. +- **Minimal permissions.** Workflows declare the least privilege they need; write + capabilities flow through gh-aw `safe-outputs:` rather than direct `permissions: + write-all`. + +[gh-aw]: https://github.com/github/gh-aw +[app-token]: https://github.com/actions/create-github-app-token diff --git a/.github/workflows/agentic_commands.yml b/.github/workflows/agentic_commands.yml index 0f3c3ac2a0..0608e31957 100644 --- a/.github/workflows/agentic_commands.yml +++ b/.github/workflows/agentic_commands.yml @@ -1,10 +1,10 @@ -# gh-aw-commands: {"payload_version":"v1","schema_version":"v1","compiler_version":"v0.79.8","commands":["analyze-build-failure"],"workflows":["build-failure-analysis-command"]} +# gh-aw-commands: {"payload_version":"v1","schema_version":"v1","compiler_version":"v0.81.6","commands":["analyze-build-failure"],"workflows":["build-failure-analysis-command"]} # Routing summary (sorted): # slash commands: # /analyze-build-failure -> build-failure-analysis-command [pull_request_comment] reaction=eyes # labels: # (none) -# This file was automatically generated by gh-aw (v0.79.8). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# This file was automatically generated by gh-aw (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -47,18 +47,21 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup Scripts - uses: github/gh-aw-actions/setup@v0.79.8 + uses: github/gh-aw-actions/setup@v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions - name: Route slash command uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_SLASH_ROUTING: '{"analyze-build-failure":[{"workflow":"build-failure-analysis-command","events":["pull_request_comment"],"ai_reaction":"eyes"}]}' + GH_AW_SLASH_ROUTING: '{"analyze-build-failure":[{"workflow":"build-failure-analysis-command","events":["pull_request_comment"],"ai_reaction":"eyes","status_comment":true}]}' GH_AW_LABEL_ROUTING: '{}' + GH_AW_HELP_COMMANDS: '[{"command":"analyze-build-failure","description":"Rerun the build-failure analysis on a pull request when a maintainer comments `/analyze-build-failure`. Same body as `build-failure-analysis.md` — re-runs `./build.sh --binaryLog`, captures the binlog, and delegates to the `build-failure-analyst` agent (which queries the binlog live via the containerized `binlog-mcp` MCP server). Useful when a previous run was cancelled, the analysis comment was dismissed, or the agent needs another pass after a force-push.","centralized":true,"decentralized":false,"source_file":"build-failure-analysis-command"}]' + GH_AW_HELP_COMMAND_ENABLED: 'true' + GH_AW_SLASH_COMMAND_DOCS_URL: 'https://github.github.com/gh-aw/reference/command-triggers/' with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index e7defdcb4c..b9e427e71d 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -1,4 +1,4 @@ -# This file was automatically generated by pkg/workflow/maintenance_workflow.go (v0.79.8). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# This file was automatically generated by pkg/workflow/maintenance_workflow.go (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -21,15 +21,17 @@ # # For more information: https://github.github.com/gh-aw/introduction/overview/ # -# Alternative regeneration methods: -# make recompile -# -# Or use the gh-aw CLI directly: -# ./gh-aw compile --validate --verbose -# -# The workflow is generated when any workflow uses the 'expires' field -# in create-discussions, create-issues, or create-pull-request safe-outputs configuration. -# Schedule frequency is automatically determined by the shortest expiration time. +# This file defines the generated agentic maintenance workflow for this repository. +# It runs scheduled cleanup for expiring safe outputs and supports manual maintenance operations. +# +# This workflow is generated automatically when workflows use expiring safe outputs +# or when repository maintenance features are enabled in .github/workflows/aw.json. +# +# To disable maintenance workflow generation, set in .github/workflows/aw.json: +# {"maintenance": false} +# +# Agentic maintenance docs: +# https://github.github.com/gh-aw/reference/ephemerals/#manual-maintenance-operations # name: Agentic Maintenance @@ -94,7 +96,7 @@ jobs: pull-requests: write steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -132,7 +134,7 @@ jobs: actions: write steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -156,12 +158,12 @@ jobs: operation: ${{ steps.record.outputs.operation }} steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -176,9 +178,9 @@ jobs: await main(); - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup-cli@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: - version: v0.79.8 + version: v0.81.6 - name: Run operation uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -196,7 +198,9 @@ jobs: - name: Record outputs id: record - run: echo "operation=${{ inputs.operation }}" >> "$GITHUB_OUTPUT" + env: + GH_AW_OPERATION: ${{ inputs.operation }} + run: echo "operation=$GH_AW_OPERATION" >> "$GITHUB_OUTPUT" update_pull_request_branches: if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'update_pull_request_branches' && (!(github.event.repository.fork)) }} @@ -206,7 +210,7 @@ jobs: pull-requests: write steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -245,14 +249,14 @@ jobs: run_url: ${{ steps.record.outputs.run_url }} steps: - name: Checkout actions folder - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: sparse-checkout: | actions persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -281,7 +285,9 @@ jobs: - name: Record outputs id: record - run: echo "run_url=${{ inputs.run_url }}" >> "$GITHUB_OUTPUT" + env: + GH_AW_RUN_URL: ${{ inputs.run_url }} + run: echo "run_url=$GH_AW_RUN_URL" >> "$GITHUB_OUTPUT" create_labels: if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'create_labels' && (!(github.event.repository.fork)) }} @@ -291,12 +297,12 @@ jobs: issues: write steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -311,9 +317,9 @@ jobs: await main(); - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup-cli@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: - version: v0.79.8 + version: v0.81.6 - name: Create missing labels uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -337,12 +343,12 @@ jobs: issues: write steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -357,9 +363,9 @@ jobs: await main(); - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup-cli@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: - version: v0.79.8 + version: v0.81.6 - name: Restore activity report logs cache id: activity_report_logs_cache @@ -378,12 +384,12 @@ jobs: GH_AW_CMD_PREFIX: gh aw run: | ${GH_AW_CMD_PREFIX} logs \ - --repo "${{ github.repository }}" \ + --repo "$GITHUB_REPOSITORY" \ --start-date -1w \ - --count 100 \ + --count 500 \ --output ./.cache/gh-aw/activity-report-logs \ --format markdown \ - > ./.cache/gh-aw/activity-report-logs/report.md + --report-file ./.cache/gh-aw/activity-report-logs/report.md - name: Save activity report logs cache if: ${{ always() }} @@ -442,12 +448,12 @@ jobs: issues: write steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -462,9 +468,9 @@ jobs: await main(); - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup-cli@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: - version: v0.79.8 + version: v0.81.6 - name: Restore forecast report logs cache id: forecast_report_logs_cache @@ -487,7 +493,7 @@ jobs: run: | mkdir -p ./.cache/gh-aw/forecast set +e - ${GH_AW_CMD_PREFIX} forecast --repo "${{ github.repository }}" --timeout 30 --verbose --json > ./.cache/gh-aw/forecast/report.json + ${GH_AW_CMD_PREFIX} forecast --repo "$GITHUB_REPOSITORY" --timeout 30 --verbose --json > ./.cache/gh-aw/forecast/report.json forecast_exit_code=$? set -e if [ "${forecast_exit_code}" -eq 124 ]; then @@ -539,7 +545,7 @@ jobs: issues: write steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -571,12 +577,12 @@ jobs: issues: write steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -591,9 +597,9 @@ jobs: await main(); - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup-cli@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: - version: v0.79.8 + version: v0.81.6 - name: Validate workflows and file issue on findings uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/.github/workflows/build-failure-analysis-command.lock.yml b/.github/workflows/build-failure-analysis-command.lock.yml index aceb182b79..94622b3978 100644 --- a/.github/workflows/build-failure-analysis-command.lock.yml +++ b/.github/workflows/build-failure-analysis-command.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"fbf91fc68f79e19baca7667dbf98adc4512ed75a27d9778a7e9d7e8317af04c2","body_hash":"ca77cb2eb75fd4d065a700e8dc75ced9a7f6c80949523a23b473afc467327cdd","compiler_version":"v0.79.8","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.60"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-dotnet","sha":"67a3573c9a986a3f9c594539f4ab511d57bb3ce9","version":"v4"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/gh-aw-actions/setup","sha":"c0338fef4749d08c21f8f975fb0e37efa17dda47","version":"v0.79.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"},{"image":"mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-binlog-mcp-amd64"}]} -# This file was automatically generated by gh-aw (v0.79.8). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"78c44c983175f30080725ed905c94d46902b87123e0b056d8d286db06b5f2cf4","body_hash":"ca77cb2eb75fd4d065a700e8dc75ced9a7f6c80949523a23b473afc467327cdd","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} +# gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-dotnet","sha":"67a3573c9a986a3f9c594539f4ab511d57bb3ce9","version":"v4"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"},{"image":"mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-binlog-mcp-amd64"}]} +# This file was automatically generated by gh-aw (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -33,30 +33,30 @@ # - NUGET_MCP_VERSION: (main workflow) # # Secrets used: -# - COPILOT_GITHUB_TOKEN # - GH_AW_GITHUB_MCP_SERVER_TOKEN # - GH_AW_GITHUB_TOKEN # - GITHUB_TOKEN # # Custom actions used: +# - actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 +# - actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 # - actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 -# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 -# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 # - actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) # - actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4 # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 -# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 # - actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 -# - github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 +# - github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 -# - ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa -# - ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c +# - ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d +# - ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d +# - ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be +# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b +# - ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 # - mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-binlog-mcp-amd64 name: "Build Failure Analysis (command)" @@ -98,6 +98,7 @@ jobs: pull-requests: write env: GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: ${{ steps.add-comment.outputs.comment-id }} @@ -109,7 +110,6 @@ jobs: engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} - secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} @@ -120,7 +120,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -130,8 +130,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Build Failure Analysis (command)" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/build-failure-analysis-command.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info @@ -139,16 +139,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AGENT_VERSION: "1.0.60" - GH_AW_INFO_CLI_VERSION: "v0.79.8" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AGENT_VERSION: "1.0.65" + GH_AW_INFO_CLI_VERSION: "v0.81.6" GH_AW_INFO_WORKFLOW_NAME: "Build Failure Analysis (command)" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","dotnet"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -159,6 +159,30 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-buildfailureanalysiscommand-${{ github.run_id }} + restore-keys: agentic-workflow-usage-buildfailureanalysiscommand- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Restore daily AIC usage cache (artifact fallback) + id: restore-daily-aic-cache-fallback + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_RESTORE_DAILY_AIC_CACHE_HIT: ${{ steps.restore-daily-aic-cache.outputs.cache-hit }} + GH_AW_RESTORE_DAILY_AIC_CACHE_MATCHED_KEY: ${{ steps.restore-daily-aic-cache.outputs.cache-matched-key }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs'); + await main(); - name: Check daily workflow token guardrail id: daily-effective-workflow-guardrail if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} @@ -168,6 +192,8 @@ jobs: GH_AW_WORKFLOW_ID: "build-failure-analysis-command" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} + GH_AW_HAS_SLASH_COMMAND: "true" + GH_AW_HAS_LABEL_COMMAND: "false" GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} with: @@ -190,13 +216,8 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/add_reaction.cjs'); await main(); - - name: Validate COPILOT_GITHUB_TOKEN secret - id: validate-secret - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Checkout .github and .agents folders - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false sparse-checkout: | @@ -232,7 +253,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.79.8" + GH_AW_COMPILED_VERSION: "v0.81.6" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -262,6 +283,9 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/add_workflow_run_comment.cjs'); await main(); + - name: Log runtime features + if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh" - name: Create prompt with built-in context env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt @@ -279,20 +303,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_522f681974152d78_EOF' + cat << 'GH_AW_PROMPT_79dda3d6a11104c4_EOF' - GH_AW_PROMPT_522f681974152d78_EOF + GH_AW_PROMPT_79dda3d6a11104c4_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_522f681974152d78_EOF' + cat << 'GH_AW_PROMPT_79dda3d6a11104c4_EOF' Tools: add_comment(max:5), create_pull_request_review_comment(max:25), missing_tool, missing_data, noop(max:5) - GH_AW_PROMPT_522f681974152d78_EOF + GH_AW_PROMPT_79dda3d6a11104c4_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_522f681974152d78_EOF' + cat << 'GH_AW_PROMPT_79dda3d6a11104c4_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -321,16 +345,16 @@ jobs: {{/if}} - GH_AW_PROMPT_522f681974152d78_EOF + GH_AW_PROMPT_79dda3d6a11104c4_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" fi - cat << 'GH_AW_PROMPT_522f681974152d78_EOF' + cat << 'GH_AW_PROMPT_79dda3d6a11104c4_EOF' {{#runtime-import .github/workflows/shared/build-failure-analysis-shared.md}} {{#runtime-import .github/workflows/build-failure-analysis-command.md}} - GH_AW_PROMPT_522f681974152d78_EOF + GH_AW_PROMPT_79dda3d6a11104c4_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -396,7 +420,7 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: activation include-hidden-files: true @@ -421,6 +445,7 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + copilot-requests: write pull-requests: read env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} @@ -428,6 +453,7 @@ jobs: GH_AW_ASSETS_BRANCH: "" GH_AW_ASSETS_MAX_SIZE_KB: 0 GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: buildfailureanalysiscommand outputs: agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} @@ -450,7 +476,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -459,8 +485,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Build Failure Analysis (command)" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/build-failure-analysis-command.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -471,7 +497,7 @@ jobs: echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false - name: Create gh-aw temp directory @@ -514,17 +540,10 @@ jobs: - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Checkout PR branch id: checkout-pr if: | @@ -540,11 +559,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) @@ -556,7 +575,7 @@ jobs: const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: activation path: /tmp/gh-aw @@ -576,15 +595,15 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-binlog-mcp-amd64 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-binlog-mcp-amd64 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_3f280d312ec7a220_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_7c2a413c8faa00fb_EOF' {"add_comment":{"hide_older_comments":true,"max":5},"create_pull_request_review_comment":{"max":25,"side":"RIGHT"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":5,"report-as-issue":"false"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_3f280d312ec7a220_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_7c2a413c8faa00fb_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -738,55 +757,17 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); await main(); - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - - name: Start MCP Gateway id: start-mcp-gateway env: + GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }} GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }} GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }} GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -eo pipefail mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" @@ -812,11 +793,11 @@ jobs: * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; esac DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.25' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --name awmg-mcpg --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.3.30' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_4dcef74ee1cb4710_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_224c223cebf60f9e_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "binlog-mcp": { @@ -838,10 +819,10 @@ jobs: }, "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.1.2", + "container": "ghcr.io/github/github-mcp-server:v1.4.0", "env": { - "GITHUB_HOST": "\${GITHUB_SERVER_URL}", - "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_HOST": "${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", "GITHUB_TOOLSETS": "pull_requests,repos" }, @@ -853,10 +834,27 @@ jobs: } }, "safeoutputs": { - "type": "http", - "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + "type": "stdio", + "container": "ghcr.io/github/gh-aw-node", + "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"], + "args": ["-w", "\${GITHUB_WORKSPACE}"], + "entrypoint": "sh", + "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], + "env": { + "DEBUG": "*", + "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", + "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", + "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", + "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", + "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", + "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", + "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", + "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", + "GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}", + "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", + "GITHUB_TOKEN": "\${GITHUB_TOKEN}", + "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", + "RUNNER_TEMP": "\${RUNNER_TEMP}" }, "guard-policies": { "write-sink": { @@ -874,7 +872,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_4dcef74ee1cb4710_EOF + GH_AW_MCP_CONFIG_224c223cebf60f9e_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -935,38 +933,43 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.vsblob.vsassets.io\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.nuget.org\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"azuresearch-usnc.nuget.org\",\"azuresearch-ussc.nuget.org\",\"builds.dotnet.microsoft.com\",\"ci.dot.net\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"dc.services.visualstudio.com\",\"dist.nuget.org\",\"dot.net\",\"dotnet.microsoft.com\",\"dotnetcli.blob.core.windows.net\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"nuget.org\",\"nuget.pkg.github.com\",\"nugetregistryv2prod.blob.core.windows.net\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"oneocsp.microsoft.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"pkgs.dev.azure.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\",\"www.microsoft.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.vsblob.vsassets.io\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.nuget.org\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"azuresearch-usnc.nuget.org\",\"azuresearch-ussc.nuget.org\",\"builds.dotnet.microsoft.com\",\"ci.dot.net\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"dc.services.visualstudio.com\",\"dist.nuget.org\",\"dot.net\",\"dotnet.microsoft.com\",\"dotnetcli.blob.core.windows.net\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"nuget.org\",\"nuget.pkg.github.com\",\"nugetregistryv2prod.blob.core.windows.net\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"oneocsp.microsoft.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"pkgs.dev.azure.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\",\"www.microsoft.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + GH_AW_CHROOT_BINARIES_SOURCE_PATH=/tmp/gh-aw GH_AW_CHROOT_IDENTITY_HOME=/tmp/gh-aw/home node "${RUNNER_TEMP}/gh-aw/actions/patch_awf_chroot_config.cjs" fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool binlog-mcp --allow-tool '\''binlog-mcp(*)'\'' --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(NuGet.Mcp.Server)'\'' --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(dotnet:*)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(find)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(printf)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(safeoutputs:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool binlog-mcp --allow-tool '\''binlog-mcp(*)'\'' --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(NuGet.Mcp.Server)'\'' --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(dotnet:*)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(find)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(printf)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(safeoutputs:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 30 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -981,6 +984,8 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Detect agent errors if: always() id: detect-agent-errors @@ -988,17 +993,10 @@ jobs: run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Copy Copilot session state files to logs if: always() continue-on-error: true @@ -1022,8 +1020,7 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); await main(); env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -1118,7 +1115,7 @@ jobs: - name: Upload agent artifacts if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: agent path: | @@ -1156,8 +1153,7 @@ jobs: - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash - # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. - run: | + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. GH_HOST="${GITHUB_SERVER_URL#https://}" @@ -1165,6 +1161,7 @@ jobs: echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: + persist-credentials: false ref: refs/pull/${{ github.event.issue.number }}/merge - name: Build with binary log id: build @@ -1221,13 +1218,14 @@ jobs: runs-on: ubuntu-slim permissions: contents: read - discussions: write issues: write pull-requests: write concurrency: group: "gh-aw-conclusion-build-failure-analysis-command" cancel-in-progress: false queue: max + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} @@ -1236,7 +1234,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1245,13 +1243,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Build Failure Analysis (command)" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/build-failure-analysis-command.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1268,34 +1266,82 @@ jobs: run: | mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection echo "Usage artifact source file status:" - for file in /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do + for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file" done + [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true + [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl + mkdir -p /tmp/gh-aw/usage/activity + node ${{ runner.temp }}/gh-aw/actions/generate_usage_activity_summary.cjs find /tmp/gh-aw/usage -type f -print | sort - name: Upload usage artifact if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: usage path: | + /tmp/gh-aw/usage/aw_info.json /tmp/gh-aw/usage/aw-info.jsonl + /tmp/gh-aw/usage/agent_usage.json /tmp/gh-aw/usage/agent_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/github_rate_limits.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl + /tmp/gh-aw/usage/activity/summary.json if-no-files-found: ignore + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache-conclusion + if: always() + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-buildfailureanalysiscommand-${{ github.run_id }} + restore-keys: agentic-workflow-usage-buildfailureanalysiscommand- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Write daily AIC usage cache entry + id: write-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context); + const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs'); + await main(); + - name: Save daily AIC usage cache + id: save-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-buildfailureanalysiscommand-${{ github.run_id }} + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Upload daily AIC usage cache artifact + id: upload-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: aic-usage-cache + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + if-no-files-found: ignore + retention-days: 7 - name: Process no-op messages id: noop uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1378,7 +1424,6 @@ jobs: GH_AW_WORKFLOW_ID: "build-failure-analysis-command" GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" - GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} @@ -1433,11 +1478,13 @@ jobs: needs: - activation - agent - if: > - always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + if: always() && needs.agent.result != 'skipped' runs-on: ubuntu-latest permissions: contents: read + copilot-requests: write + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: aic: ${{ steps.parse_detection_token_usage.outputs.aic }} detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} @@ -1446,7 +1493,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1455,13 +1502,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Build Failure Analysis (command)" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/build-failure-analysis-command.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1474,7 +1521,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Checkout repository for patch context if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false # --- Threat Detection --- @@ -1483,7 +1530,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d - name: Check if detection needed id: detection_guard if: always() @@ -1546,11 +1593,11 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1569,37 +1616,44 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + _GH_AW_CHROOT_JSON=$(jq -c --arg src /tmp/gh-aw --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home /tmp/gh-aw/home '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; } + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "/tmp/gh-aw/awf-config.json" fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1613,6 +1667,8 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Parse threat detection token usage for step summary id: parse_detection_token_usage if: always() @@ -1628,7 +1684,7 @@ jobs: await main(); - name: Upload threat detection log if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: detection path: /tmp/gh-aw/threat-detection/detection.log @@ -1669,6 +1725,8 @@ jobs: pre_activation: needs: build runs-on: ubuntu-slim + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' && steps.check_command_position.outputs.command_position_ok == 'true' }} matched_command: ${{ steps.check_command_position.outputs.matched_command }} @@ -1678,15 +1736,15 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} env: GH_AW_SETUP_WORKFLOW_NAME: "Build Failure Analysis (command)" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/build-failure-analysis-command.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Check team membership for command workflow id: check_membership @@ -1721,7 +1779,6 @@ jobs: runs-on: ubuntu-slim permissions: contents: read - discussions: write issues: write pull-requests: write timeout-minutes: 45 @@ -1736,7 +1793,8 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.60" + GH_AW_ENGINE_VERSION: "1.0.65" + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "build-failure-analysis-command" GH_AW_WORKFLOW_NAME: "Build Failure Analysis (command)" @@ -1753,7 +1811,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1762,13 +1820,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Build Failure Analysis (command)" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/build-failure-analysis-command.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1782,8 +1840,7 @@ jobs: - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash - # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. - run: | + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. GH_HOST="${GITHUB_SERVER_URL#https://}" @@ -1808,7 +1865,7 @@ jobs: await main(); - name: Upload Safe Outputs Items if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: safe-outputs-items path: | diff --git a/.github/workflows/build-failure-analysis-command.md b/.github/workflows/build-failure-analysis-command.md index 1e1a7fa75c..d69680c778 100644 --- a/.github/workflows/build-failure-analysis-command.md +++ b/.github/workflows/build-failure-analysis-command.md @@ -30,6 +30,7 @@ if: needs.build.outputs.outcome == 'failure' permissions: contents: read pull-requests: read + copilot-requests: write concurrency: group: build-failure-analysis-${{ github.event.issue.number }} diff --git a/.github/workflows/build-failure-analysis.lock.yml b/.github/workflows/build-failure-analysis.lock.yml index 79a44fd4fd..2f106f1f31 100644 --- a/.github/workflows/build-failure-analysis.lock.yml +++ b/.github/workflows/build-failure-analysis.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"087c2353e521c19649ce6903d3471d51e55ba79d6e6f92fd8b74f1b08d3397aa","body_hash":"be3df9fbfc93f1e74db1f27a55d03f88a2a6b6ba98ca0153718240e964956a0d","compiler_version":"v0.79.8","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.60"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-dotnet","sha":"67a3573c9a986a3f9c594539f4ab511d57bb3ce9","version":"v4"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/gh-aw-actions/setup","sha":"c0338fef4749d08c21f8f975fb0e37efa17dda47","version":"v0.79.8"},{"repo":"github/gh-aw/actions/setup-cli","sha":"8b02ab336d100a5746e9f53b8bc2b22878278a6f","version":"v0.79.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"},{"image":"mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-binlog-mcp-amd64"}]} -# This file was automatically generated by gh-aw (v0.79.8). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"82d15dd6a5536335875fb0ac3020797659abd61282c91df9771d49e2652f26a9","body_hash":"be3df9fbfc93f1e74db1f27a55d03f88a2a6b6ba98ca0153718240e964956a0d","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} +# gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-dotnet","sha":"67a3573c9a986a3f9c594539f4ab511d57bb3ce9","version":"v4"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"},{"repo":"github/gh-aw-actions/setup-cli","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"},{"image":"mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-binlog-mcp-amd64"}]} +# This file was automatically generated by gh-aw (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -33,31 +33,31 @@ # - NUGET_MCP_VERSION: (main workflow) # # Secrets used: -# - COPILOT_GITHUB_TOKEN # - GH_AW_GITHUB_MCP_SERVER_TOKEN # - GH_AW_GITHUB_TOKEN # - GITHUB_TOKEN # # Custom actions used: +# - actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 +# - actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 # - actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 -# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 -# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 # - actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) # - actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4 # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 -# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 # - actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 -# - github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 -# - github/gh-aw/actions/setup-cli@8b02ab336d100a5746e9f53b8bc2b22878278a6f # v0.79.8 +# - github/gh-aw-actions/setup-cli@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 +# - github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 -# - ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa -# - ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c +# - ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d +# - ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d +# - ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be +# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b +# - ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 # - mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-binlog-mcp-amd64 name: "Build Failure Analysis" @@ -114,6 +114,7 @@ jobs: pull-requests: write env: GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: "" @@ -124,7 +125,6 @@ jobs: engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} - secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} @@ -134,7 +134,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -144,8 +144,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Build Failure Analysis" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/build-failure-analysis.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info @@ -153,16 +153,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AGENT_VERSION: "1.0.60" - GH_AW_INFO_CLI_VERSION: "v0.79.8" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AGENT_VERSION: "1.0.65" + GH_AW_INFO_CLI_VERSION: "v0.81.6" GH_AW_INFO_WORKFLOW_NAME: "Build Failure Analysis" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","dotnet"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -173,6 +173,30 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-buildfailureanalysis-${{ github.run_id }} + restore-keys: agentic-workflow-usage-buildfailureanalysis- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Restore daily AIC usage cache (artifact fallback) + id: restore-daily-aic-cache-fallback + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_RESTORE_DAILY_AIC_CACHE_HIT: ${{ steps.restore-daily-aic-cache.outputs.cache-hit }} + GH_AW_RESTORE_DAILY_AIC_CACHE_MATCHED_KEY: ${{ steps.restore-daily-aic-cache.outputs.cache-matched-key }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs'); + await main(); - name: Check daily workflow token guardrail id: daily-effective-workflow-guardrail if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} @@ -182,6 +206,8 @@ jobs: GH_AW_WORKFLOW_ID: "build-failure-analysis" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} + GH_AW_HAS_SLASH_COMMAND: "false" + GH_AW_HAS_LABEL_COMMAND: "false" GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} with: @@ -204,13 +230,8 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/add_reaction.cjs'); await main(); - - name: Validate COPILOT_GITHUB_TOKEN secret - id: validate-secret - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Checkout .github and .agents folders - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false sparse-checkout: | @@ -246,7 +267,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.79.8" + GH_AW_COMPILED_VERSION: "v0.81.6" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -264,6 +285,9 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/compute_text.cjs'); await main(); + - name: Log runtime features + if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh" - name: Create prompt with built-in context env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt @@ -280,20 +304,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_35adcbf6b0570e40_EOF' + cat << 'GH_AW_PROMPT_d71d5ffb8c5efd70_EOF' - GH_AW_PROMPT_35adcbf6b0570e40_EOF + GH_AW_PROMPT_d71d5ffb8c5efd70_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_35adcbf6b0570e40_EOF' + cat << 'GH_AW_PROMPT_d71d5ffb8c5efd70_EOF' Tools: add_comment(max:5), create_pull_request_review_comment(max:25), missing_tool, missing_data, noop(max:5) - GH_AW_PROMPT_35adcbf6b0570e40_EOF + GH_AW_PROMPT_d71d5ffb8c5efd70_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_35adcbf6b0570e40_EOF' + cat << 'GH_AW_PROMPT_d71d5ffb8c5efd70_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -322,13 +346,13 @@ jobs: {{/if}} - GH_AW_PROMPT_35adcbf6b0570e40_EOF + GH_AW_PROMPT_d71d5ffb8c5efd70_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_35adcbf6b0570e40_EOF' + cat << 'GH_AW_PROMPT_d71d5ffb8c5efd70_EOF' {{#runtime-import .github/workflows/shared/build-failure-analysis-shared.md}} {{#runtime-import .github/workflows/build-failure-analysis.md}} - GH_AW_PROMPT_35adcbf6b0570e40_EOF + GH_AW_PROMPT_d71d5ffb8c5efd70_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -390,7 +414,7 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: activation include-hidden-files: true @@ -417,6 +441,7 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + copilot-requests: write pull-requests: read env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} @@ -424,6 +449,7 @@ jobs: GH_AW_ASSETS_BRANCH: "" GH_AW_ASSETS_MAX_SIZE_KB: 0 GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: buildfailureanalysis outputs: agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} @@ -446,7 +472,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -455,8 +481,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Build Failure Analysis" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/build-failure-analysis.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -467,13 +493,13 @@ jobs: echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false - name: Install gh-aw extension - uses: github/gh-aw/actions/setup-cli@8b02ab336d100a5746e9f53b8bc2b22878278a6f # v0.79.8 + uses: github/gh-aw-actions/setup-cli@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: - version: 'v0.79.8' + version: 'v0.81.6' - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise @@ -515,17 +541,10 @@ jobs: - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Checkout PR branch id: checkout-pr if: | @@ -541,11 +560,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) @@ -557,7 +576,7 @@ jobs: const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: activation path: /tmp/gh-aw @@ -577,15 +596,15 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-binlog-mcp-amd64 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 mcr.microsoft.com/dotnet-buildtools/prereqs:azurelinux-3.0-binlog-mcp-amd64 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_3f280d312ec7a220_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_7c2a413c8faa00fb_EOF' {"add_comment":{"hide_older_comments":true,"max":5},"create_pull_request_review_comment":{"max":25,"side":"RIGHT"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":5,"report-as-issue":"false"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_3f280d312ec7a220_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_7c2a413c8faa00fb_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -739,55 +758,17 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); await main(); - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - - name: Start MCP Gateway id: start-mcp-gateway env: + GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }} GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }} GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }} GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -eo pipefail mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" @@ -813,11 +794,11 @@ jobs: * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; esac DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.25' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --name awmg-mcpg --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.3.30' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_4dcef74ee1cb4710_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_224c223cebf60f9e_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "binlog-mcp": { @@ -839,10 +820,10 @@ jobs: }, "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.1.2", + "container": "ghcr.io/github/github-mcp-server:v1.4.0", "env": { - "GITHUB_HOST": "\${GITHUB_SERVER_URL}", - "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_HOST": "${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", "GITHUB_TOOLSETS": "pull_requests,repos" }, @@ -854,10 +835,27 @@ jobs: } }, "safeoutputs": { - "type": "http", - "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + "type": "stdio", + "container": "ghcr.io/github/gh-aw-node", + "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"], + "args": ["-w", "\${GITHUB_WORKSPACE}"], + "entrypoint": "sh", + "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], + "env": { + "DEBUG": "*", + "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", + "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", + "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", + "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", + "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", + "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", + "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", + "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", + "GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}", + "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", + "GITHUB_TOKEN": "\${GITHUB_TOKEN}", + "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", + "RUNNER_TEMP": "\${RUNNER_TEMP}" }, "guard-policies": { "write-sink": { @@ -875,7 +873,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_4dcef74ee1cb4710_EOF + GH_AW_MCP_CONFIG_224c223cebf60f9e_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -936,38 +934,43 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.vsblob.vsassets.io\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.nuget.org\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"azuresearch-usnc.nuget.org\",\"azuresearch-ussc.nuget.org\",\"builds.dotnet.microsoft.com\",\"ci.dot.net\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"dc.services.visualstudio.com\",\"dist.nuget.org\",\"dot.net\",\"dotnet.microsoft.com\",\"dotnetcli.blob.core.windows.net\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"nuget.org\",\"nuget.pkg.github.com\",\"nugetregistryv2prod.blob.core.windows.net\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"oneocsp.microsoft.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"pkgs.dev.azure.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\",\"www.microsoft.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.vsblob.vsassets.io\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.nuget.org\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"azuresearch-usnc.nuget.org\",\"azuresearch-ussc.nuget.org\",\"builds.dotnet.microsoft.com\",\"ci.dot.net\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"dc.services.visualstudio.com\",\"dist.nuget.org\",\"dot.net\",\"dotnet.microsoft.com\",\"dotnetcli.blob.core.windows.net\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"nuget.org\",\"nuget.pkg.github.com\",\"nugetregistryv2prod.blob.core.windows.net\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"oneocsp.microsoft.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"pkgs.dev.azure.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\",\"www.microsoft.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + GH_AW_CHROOT_BINARIES_SOURCE_PATH=/tmp/gh-aw GH_AW_CHROOT_IDENTITY_HOME=/tmp/gh-aw/home node "${RUNNER_TEMP}/gh-aw/actions/patch_awf_chroot_config.cjs" fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool binlog-mcp --allow-tool '\''binlog-mcp(*)'\'' --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(NuGet.Mcp.Server)'\'' --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(dotnet:*)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(find)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(printf)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(safeoutputs:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool binlog-mcp --allow-tool '\''binlog-mcp(*)'\'' --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(NuGet.Mcp.Server)'\'' --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(dotnet:*)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(find)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(printf)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(safeoutputs:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 30 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -982,6 +985,8 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Detect agent errors if: always() id: detect-agent-errors @@ -989,17 +994,10 @@ jobs: run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Copy Copilot session state files to logs if: always() continue-on-error: true @@ -1023,8 +1021,7 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); await main(); env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -1118,7 +1115,7 @@ jobs: - name: Upload agent artifacts if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: agent path: | @@ -1157,14 +1154,15 @@ jobs: - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash - # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. - run: | + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. GH_HOST="${GITHUB_SERVER_URL#https://}" GH_HOST="${GH_HOST#http://}" echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - name: Build with binary log id: build run: | @@ -1220,13 +1218,14 @@ jobs: runs-on: ubuntu-slim permissions: contents: read - discussions: write issues: write pull-requests: write concurrency: group: "gh-aw-conclusion-build-failure-analysis" cancel-in-progress: false queue: max + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} @@ -1235,7 +1234,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1244,13 +1243,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Build Failure Analysis" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/build-failure-analysis.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1267,34 +1266,82 @@ jobs: run: | mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection echo "Usage artifact source file status:" - for file in /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do + for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file" done + [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true + [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl + mkdir -p /tmp/gh-aw/usage/activity + node ${{ runner.temp }}/gh-aw/actions/generate_usage_activity_summary.cjs find /tmp/gh-aw/usage -type f -print | sort - name: Upload usage artifact if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: usage path: | + /tmp/gh-aw/usage/aw_info.json /tmp/gh-aw/usage/aw-info.jsonl + /tmp/gh-aw/usage/agent_usage.json /tmp/gh-aw/usage/agent_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/github_rate_limits.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl + /tmp/gh-aw/usage/activity/summary.json + if-no-files-found: ignore + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache-conclusion + if: always() + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-buildfailureanalysis-${{ github.run_id }} + restore-keys: agentic-workflow-usage-buildfailureanalysis- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Write daily AIC usage cache entry + id: write-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context); + const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs'); + await main(); + - name: Save daily AIC usage cache + id: save-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-buildfailureanalysis-${{ github.run_id }} + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Upload daily AIC usage cache artifact + id: upload-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: aic-usage-cache + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl if-no-files-found: ignore + retention-days: 7 - name: Process no-op messages id: noop uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1377,7 +1424,6 @@ jobs: GH_AW_WORKFLOW_ID: "build-failure-analysis" GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" - GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} @@ -1412,11 +1458,13 @@ jobs: needs: - activation - agent - if: > - always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + if: always() && needs.agent.result != 'skipped' runs-on: ubuntu-latest permissions: contents: read + copilot-requests: write + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: aic: ${{ steps.parse_detection_token_usage.outputs.aic }} detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} @@ -1425,7 +1473,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1434,13 +1482,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Build Failure Analysis" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/build-failure-analysis.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1453,7 +1501,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Checkout repository for patch context if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false # --- Threat Detection --- @@ -1462,7 +1510,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d - name: Check if detection needed id: detection_guard if: always() @@ -1525,11 +1573,11 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1548,37 +1596,44 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + _GH_AW_CHROOT_JSON=$(jq -c --arg src /tmp/gh-aw --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home /tmp/gh-aw/home '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; } + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "/tmp/gh-aw/awf-config.json" fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1592,6 +1647,8 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Parse threat detection token usage for step summary id: parse_detection_token_usage if: always() @@ -1607,7 +1664,7 @@ jobs: await main(); - name: Upload threat detection log if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: detection path: /tmp/gh-aw/threat-detection/detection.log @@ -1648,6 +1705,8 @@ jobs: pre_activation: needs: build runs-on: ubuntu-slim + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} matched_command: '' @@ -1657,15 +1716,15 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} env: GH_AW_SETUP_WORKFLOW_NAME: "Build Failure Analysis" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/build-failure-analysis.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Check team membership for workflow id: check_membership @@ -1689,7 +1748,6 @@ jobs: runs-on: ubuntu-slim permissions: contents: read - discussions: write issues: write pull-requests: write timeout-minutes: 45 @@ -1703,7 +1761,8 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.60" + GH_AW_ENGINE_VERSION: "1.0.65" + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "build-failure-analysis" GH_AW_WORKFLOW_NAME: "Build Failure Analysis" @@ -1720,7 +1779,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1729,13 +1788,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Build Failure Analysis" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/build-failure-analysis.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1749,8 +1808,7 @@ jobs: - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash - # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. - run: | + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. GH_HOST="${GITHUB_SERVER_URL#https://}" @@ -1775,7 +1833,7 @@ jobs: await main(); - name: Upload Safe Outputs Items if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: safe-outputs-items path: | diff --git a/.github/workflows/build-failure-analysis.md b/.github/workflows/build-failure-analysis.md index a859cb05ea..145c0f8f01 100644 --- a/.github/workflows/build-failure-analysis.md +++ b/.github/workflows/build-failure-analysis.md @@ -50,6 +50,7 @@ if: needs.build.outputs.outcome == 'failure' permissions: contents: read pull-requests: read + copilot-requests: write concurrency: group: build-failure-analysis-${{ github.event.pull_request.number || github.event.issue.number || github.ref }} diff --git a/.github/workflows/code-simplifier.lock.yml b/.github/workflows/code-simplifier.lock.yml index 2f622631dd..c16e8944a8 100644 --- a/.github/workflows/code-simplifier.lock.yml +++ b/.github/workflows/code-simplifier.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"a58b14a6f253fdf01df7f96d2bbc021ead22f89f93bb00172eebc4a631870409","body_hash":"87c74b7dd1d37e030f1b1a7728439ebd357ea558679a47e76c1edd7acf187300","compiler_version":"v0.79.8","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.60"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"c0338fef4749d08c21f8f975fb0e37efa17dda47","version":"v0.79.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]} -# This file was automatically generated by gh-aw (v0.79.8). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"e8cef3855c2562e8df4521bdbd92953c34b3dbbeb2113fd451da8fcd4bba4ffa","body_hash":"87c74b7dd1d37e030f1b1a7728439ebd357ea558679a47e76c1edd7acf187300","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} +# gh-aw-manifest: {"version":1,"secrets":["GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} +# This file was automatically generated by gh-aw (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -31,27 +31,30 @@ # - shared/reporting.md # # Secrets used: -# - COPILOT_GITHUB_TOKEN # - GH_AW_CI_TRIGGER_TOKEN # - GH_AW_GITHUB_MCP_SERVER_TOKEN # - GH_AW_GITHUB_TOKEN # - GITHUB_TOKEN # # Custom actions used: -# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 -# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 +# - actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 +# - actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 +# - actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 -# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 +# - actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 +# - github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 -# - ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa -# - ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c +# - ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d +# - ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d +# - ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be +# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b +# - ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 name: "Code Simplifier" on: @@ -84,6 +87,7 @@ jobs: contents: read env: GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: comment_id: "" comment_repo: "" @@ -93,7 +97,6 @@ jobs: engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} - secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} @@ -101,7 +104,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -111,8 +114,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Code Simplifier" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/code-simplifier.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info @@ -120,16 +123,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AGENT_VERSION: "1.0.60" - GH_AW_INFO_CLI_VERSION: "v0.79.8" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AGENT_VERSION: "1.0.65" + GH_AW_INFO_CLI_VERSION: "v0.81.6" GH_AW_INFO_WORKFLOW_NAME: "Code Simplifier" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","dotnet"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -140,6 +143,30 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-codesimplifier-${{ github.run_id }} + restore-keys: agentic-workflow-usage-codesimplifier- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Restore daily AIC usage cache (artifact fallback) + id: restore-daily-aic-cache-fallback + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_RESTORE_DAILY_AIC_CACHE_HIT: ${{ steps.restore-daily-aic-cache.outputs.cache-hit }} + GH_AW_RESTORE_DAILY_AIC_CACHE_MATCHED_KEY: ${{ steps.restore-daily-aic-cache.outputs.cache-matched-key }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs'); + await main(); - name: Check daily workflow token guardrail id: daily-effective-workflow-guardrail if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} @@ -149,6 +176,8 @@ jobs: GH_AW_WORKFLOW_ID: "code-simplifier" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} + GH_AW_HAS_SLASH_COMMAND: "false" + GH_AW_HAS_LABEL_COMMAND: "false" GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} with: @@ -158,13 +187,8 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs'); await main(); - - name: Validate COPILOT_GITHUB_TOKEN secret - id: validate-secret - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Checkout .github and .agents folders - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false sparse-checkout: | @@ -200,13 +224,16 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.79.8" + GH_AW_COMPILED_VERSION: "v0.81.6" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); await main(); + - name: Log runtime features + if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh" - name: Create prompt with built-in context env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt @@ -223,23 +250,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_912555b0d35ade5f_EOF' + cat << 'GH_AW_PROMPT_6d596ee975551232_EOF' - GH_AW_PROMPT_912555b0d35ade5f_EOF + GH_AW_PROMPT_6d596ee975551232_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_912555b0d35ade5f_EOF' + cat << 'GH_AW_PROMPT_6d596ee975551232_EOF' Tools: create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_912555b0d35ade5f_EOF + GH_AW_PROMPT_6d596ee975551232_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_912555b0d35ade5f_EOF' + cat << 'GH_AW_PROMPT_6d596ee975551232_EOF' - GH_AW_PROMPT_912555b0d35ade5f_EOF + GH_AW_PROMPT_6d596ee975551232_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_912555b0d35ade5f_EOF' + cat << 'GH_AW_PROMPT_6d596ee975551232_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -268,14 +295,14 @@ jobs: {{/if}} - GH_AW_PROMPT_912555b0d35ade5f_EOF + GH_AW_PROMPT_6d596ee975551232_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_912555b0d35ade5f_EOF' + cat << 'GH_AW_PROMPT_6d596ee975551232_EOF' {{#runtime-import .github/workflows/shared/formatting.md}} {{#runtime-import .github/workflows/shared/reporting.md}} {{#runtime-import .github/workflows/code-simplifier.md}} - GH_AW_PROMPT_912555b0d35ade5f_EOF + GH_AW_PROMPT_6d596ee975551232_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -339,7 +366,7 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: activation include-hidden-files: true @@ -360,7 +387,23 @@ jobs: needs: activation if: needs.activation.outputs.daily_ai_credits_exceeded != 'true' runs-on: ubuntu-latest - permissions: read-all + permissions: + actions: read + attestations: read + checks: read + contents: read + copilot-requests: write + deployments: read + discussions: read + issues: read + models: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read + vulnerability-alerts: read concurrency: group: "gh-aw-copilot-${{ github.workflow }}" queue: max @@ -370,6 +413,7 @@ jobs: GH_AW_ASSETS_BRANCH: "" GH_AW_ASSETS_MAX_SIZE_KB: 0 GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: codesimplifier outputs: agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} @@ -392,7 +436,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -401,8 +445,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Code Simplifier" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/code-simplifier.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -413,7 +457,7 @@ jobs: echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false - name: Create gh-aw temp directory @@ -424,17 +468,10 @@ jobs: GH_TOKEN: ${{ github.token }} - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Checkout PR branch id: checkout-pr if: | @@ -450,11 +487,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) @@ -466,7 +503,7 @@ jobs: const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: activation path: /tmp/gh-aw @@ -486,15 +523,15 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_8f4a46b9c7f8fb9a_EOF' - {"create_pull_request":{"expires":24,"labels":["agentic-workflows"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"fallback-to-issue","title_prefix":"[code-simplifier] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_8f4a46b9c7f8fb9a_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_cffa68fb200923bd_EOF' + {"create_pull_request":{"expires":24,"labels":["agentic-workflows"],"max":1,"max_patch_files":100,"max_patch_size":4096,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"fallback-to-issue","title_prefix":"[code-simplifier] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_cffa68fb200923bd_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -629,55 +666,17 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); await main(); - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - - name: Start MCP Gateway id: start-mcp-gateway env: + GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }} GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }} GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }} GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -eo pipefail mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" @@ -703,19 +702,19 @@ jobs: * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; esac DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.25' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --name awmg-mcpg --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.3.30' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_c6fee03c27b97257_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_f014db59cae17bc3_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.1.2", + "container": "ghcr.io/github/github-mcp-server:v1.4.0", "env": { - "GITHUB_HOST": "\${GITHUB_SERVER_URL}", - "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_HOST": "${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", "GITHUB_TOOLSETS": "context,repos,issues,pull_requests" }, @@ -727,10 +726,27 @@ jobs: } }, "safeoutputs": { - "type": "http", - "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + "type": "stdio", + "container": "ghcr.io/github/gh-aw-node", + "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"], + "args": ["-w", "\${GITHUB_WORKSPACE}"], + "entrypoint": "sh", + "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], + "env": { + "DEBUG": "*", + "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", + "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", + "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", + "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", + "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", + "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", + "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", + "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", + "GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}", + "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", + "GITHUB_TOKEN": "\${GITHUB_TOKEN}", + "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", + "RUNNER_TEMP": "\${RUNNER_TEMP}" }, "guard-policies": { "write-sink": { @@ -748,7 +764,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_c6fee03c27b97257_EOF + GH_AW_MCP_CONFIG_f014db59cae17bc3_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -787,37 +803,41 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json","network":{"allowDomains":["*.vsblob.vsassets.io","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.nuget.org","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","azuresearch-usnc.nuget.org","azuresearch-ussc.nuget.org","builds.dotnet.microsoft.com","ci.dot.net","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","dc.services.visualstudio.com","dist.nuget.org","dot.net","dotnet.microsoft.com","dotnetcli.blob.core.windows.net","github.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","nuget.org","nuget.pkg.github.com","nugetregistryv2prod.blob.core.windows.net","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","oneocsp.microsoft.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","pkgs.dev.azure.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com","www.microsoft.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxAiCredits":2000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5.3","gemini-pro","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"claude":["agent"],"codex":["agent"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"computer-use":["copilot/*computer-use*","google/*computer-use*","gemini/*computer-use*","openai/*computer-use*"],"copilot":["agent"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent"],"gemini-3-flash":["copilot/gemini-3*flash*","google/gemini-3*flash*","gemini/gemini-3*flash*"],"gemini-3-pro":["copilot/gemini-3*pro*","google/gemini-3*pro*","google/nano-banana*","gemini/gemini-3*pro*"],"gemini-3.1-flash":["copilot/gemini-3.1*flash*","google/gemini-3.1*flash*","gemini/gemini-3.1*flash*"],"gemini-3.1-pro":["copilot/gemini-3.1*pro*","google/gemini-3.1*pro*","gemini/gemini-3.1*pro*"],"gemini-3.5-flash":["copilot/gemini-3.5*flash*","google/gemini-3.5*flash*","gemini/gemini-3.5*flash*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"gpt-5.2":["copilot/gpt-5.2*","openai/gpt-5.2*"],"gpt-5.3":["copilot/gpt-5.3*","openai/gpt-5.3*"],"gpt-5.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"gpt-5.5":["copilot/gpt-5.5*","openai/gpt-5.5*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mai-code":["copilot/MAI-Code*","copilot/mai-code*","openai/MAI-Code*"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"nano-banana":["copilot/nano-banana*","google/nano-banana*","gemini/nano-banana*"],"opus":["copilot/*opus*","anthropic/*opus*"],"opusplan":["opus?effort=high"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"small-agent":["haiku","gpt-5-mini","gemini-flash"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4.5*","copilot/*sonnet-4.6*","copilot/*sonnet-4-5-*","anthropic/*sonnet-4-5-*","copilot/*sonnet-4-6*","anthropic/*sonnet-4-6*"],"summarization":["haiku","gpt-5-mini","gemini-flash-lite","mini"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json","network":{"allowDomains":["*.vsblob.vsassets.io","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.nuget.org","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","azuresearch-usnc.nuget.org","azuresearch-ussc.nuget.org","builds.dotnet.microsoft.com","ci.dot.net","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","dc.services.visualstudio.com","dist.nuget.org","dot.net","dotnet.microsoft.com","dotnetcli.blob.core.windows.net","github.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","nuget.org","nuget.pkg.github.com","nugetregistryv2prod.blob.core.windows.net","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","oneocsp.microsoft.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","pkgs.dev.azure.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com","www.microsoft.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxCacheMisses":5,"maxAiCredits":2000,"models":{"agent":["sonnet-6x","gpt-5.5","gpt-5.4","gpt-5.3","gemini-pro","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"claude":["agent"],"codex":["agent"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"computer-use":["copilot/*computer-use*","google/*computer-use*","gemini/*computer-use*","openai/*computer-use*"],"copilot":["agent"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent"],"gemini-3-flash":["copilot/gemini-3*flash*","google/gemini-3*flash*","gemini/gemini-3*flash*"],"gemini-3-pro":["copilot/gemini-3*pro*","google/gemini-3*pro*","google/nano-banana*","gemini/gemini-3*pro*"],"gemini-3.1-flash":["copilot/gemini-3.1*flash*","google/gemini-3.1*flash*","gemini/gemini-3.1*flash*"],"gemini-3.1-pro":["copilot/gemini-3.1*pro*","google/gemini-3.1*pro*","gemini/gemini-3.1*pro*"],"gemini-3.5-flash":["copilot/gemini-3.5*flash*","google/gemini-3.5*flash*","gemini/gemini-3.5*flash*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"gpt-5.1":["copilot/gpt-5.1*","openai/gpt-5.1*"],"gpt-5.2":["copilot/gpt-5.2*","openai/gpt-5.2*"],"gpt-5.3":["copilot/gpt-5.3*","openai/gpt-5.3*"],"gpt-5.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"gpt-5.5":["copilot/gpt-5.5*","openai/gpt-5.5*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"image-generation":["copilot/gpt-image*","openai/gpt-image*","openai/chatgpt-image*","copilot/gemini-*image*","google/gemini-*image*","gemini/gemini-*image*","google/imagen*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mai-code":["copilot/MAI-Code*","copilot/mai-code*","openai/MAI-Code*"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"nano-banana":["copilot/nano-banana*","google/nano-banana*","gemini/nano-banana*"],"opus":["copilot/*opus*","anthropic/*opus*"],"opusplan":["opus?effort=high"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"small-agent":["haiku","gpt-5-mini","gemini-flash"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4.5*","copilot/*sonnet-4.6*","copilot/*sonnet-4-5-*","anthropic/*sonnet-4-5-*","copilot/*sonnet-4-6*","anthropic/*sonnet-4-6*"],"summarization":["haiku","gpt-5-mini","gemini-flash-lite","mini"],"vision":["copilot/gemini-*image*","google/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + GH_AW_CHROOT_BINARIES_SOURCE_PATH=/tmp/gh-aw GH_AW_CHROOT_IDENTITY_HOME=/tmp/gh-aw/home node "${RUNNER_TEMP}/gh-aw/actions/patch_awf_chroot_config.cjs" fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 30 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -832,6 +852,8 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Detect agent errors if: always() id: detect-agent-errors @@ -839,17 +861,10 @@ jobs: run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Copy Copilot session state files to logs if: always() continue-on-error: true @@ -873,8 +888,7 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); await main(); env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -968,7 +982,7 @@ jobs: - name: Upload agent artifacts if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: agent path: | @@ -1009,6 +1023,8 @@ jobs: group: "gh-aw-conclusion-code-simplifier" cancel-in-progress: false queue: max + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} @@ -1017,7 +1033,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1026,13 +1042,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Code Simplifier" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/code-simplifier.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1049,34 +1065,82 @@ jobs: run: | mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection echo "Usage artifact source file status:" - for file in /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do + for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file" done + [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true + [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl + mkdir -p /tmp/gh-aw/usage/activity + node ${{ runner.temp }}/gh-aw/actions/generate_usage_activity_summary.cjs find /tmp/gh-aw/usage -type f -print | sort - name: Upload usage artifact if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: usage path: | + /tmp/gh-aw/usage/aw_info.json /tmp/gh-aw/usage/aw-info.jsonl + /tmp/gh-aw/usage/agent_usage.json /tmp/gh-aw/usage/agent_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/github_rate_limits.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl + /tmp/gh-aw/usage/activity/summary.json if-no-files-found: ignore + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache-conclusion + if: always() + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-codesimplifier-${{ github.run_id }} + restore-keys: agentic-workflow-usage-codesimplifier- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Write daily AIC usage cache entry + id: write-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context); + const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs'); + await main(); + - name: Save daily AIC usage cache + id: save-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-codesimplifier-${{ github.run_id }} + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Upload daily AIC usage cache artifact + id: upload-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: aic-usage-cache + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + if-no-files-found: ignore + retention-days: 7 - name: Process no-op messages id: noop uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1164,7 +1228,6 @@ jobs: GH_AW_WORKFLOW_ID: "code-simplifier" GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" - GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} @@ -1201,11 +1264,13 @@ jobs: needs: - activation - agent - if: > - always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + if: always() && needs.agent.result != 'skipped' runs-on: ubuntu-latest permissions: contents: read + copilot-requests: write + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: aic: ${{ steps.parse_detection_token_usage.outputs.aic }} detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} @@ -1214,7 +1279,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1223,13 +1288,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Code Simplifier" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/code-simplifier.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1242,7 +1307,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Checkout repository for patch context if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false # --- Threat Detection --- @@ -1251,7 +1316,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d - name: Check if detection needed id: detection_guard if: always() @@ -1314,11 +1379,11 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1337,37 +1402,44 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + _GH_AW_CHROOT_JSON=$(jq -c --arg src /tmp/gh-aw --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home /tmp/gh-aw/home '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; } + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "/tmp/gh-aw/awf-config.json" fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1381,6 +1453,8 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Parse threat detection token usage for step summary id: parse_detection_token_usage if: always() @@ -1396,7 +1470,7 @@ jobs: await main(); - name: Upload threat detection log if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: detection path: /tmp/gh-aw/threat-detection/detection.log @@ -1436,6 +1510,8 @@ jobs: pre_activation: runs-on: ubuntu-slim + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' && steps.check_skip_if_match.outputs.skip_check_ok == 'true' }} matched_command: '' @@ -1445,15 +1521,15 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} env: GH_AW_SETUP_WORKFLOW_NAME: "Code Simplifier" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/code-simplifier.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Check team membership for workflow id: check_membership @@ -1503,7 +1579,8 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.60" + GH_AW_ENGINE_VERSION: "1.0.65" + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_TRACKER_ID: "code-simplifier" GH_AW_WORKFLOW_ID: "code-simplifier" @@ -1521,7 +1598,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1530,13 +1607,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Code Simplifier" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/code-simplifier.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1549,55 +1626,27 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Download patch artifact continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ - - name: Extract base branch from agent output - id: extract-base-branch - if: steps.download-agent-output.outcome == 'success' - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/extract_base_branch_from_agent_output.cjs'); - await main(); - - name: Checkout repository (trusted default branch for comment events) - if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') && (github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - ref: ${{ github.event.repository.default_branch }} - token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - persist-credentials: false - fetch-depth: 1 - name: Checkout repository - if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') && github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: - ref: ${{ steps.extract-base-branch.outputs.base-branch || github.base_ref || github.event.pull_request.base.ref || github.ref_name || github.event.repository.default_branch }} + persist-credentials: true token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - persist-credentials: false - fetch-depth: 1 - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GIT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GIT_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash - # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. - run: | + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. GH_HOST="${GITHUB_SERVER_URL#https://}" @@ -1612,7 +1661,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"expires\":24,\"labels\":[\"agentic-workflows\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"fallback-to-issue\",\"title_prefix\":\"[code-simplifier] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"expires\":24,\"labels\":[\"agentic-workflows\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"fallback-to-issue\",\"title_prefix\":\"[code-simplifier] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -1623,7 +1672,7 @@ jobs: await main(); - name: Upload Safe Outputs Items if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: safe-outputs-items path: | diff --git a/.github/workflows/code-simplifier.md b/.github/workflows/code-simplifier.md index e75bc72aa7..e1021c5471 100644 --- a/.github/workflows/code-simplifier.md +++ b/.github/workflows/code-simplifier.md @@ -2,7 +2,23 @@ on: schedule: daily skip-if-match: is:pr is:open in:title "[code-simplifier]" -permissions: read-all +permissions: + actions: read + attestations: read + checks: read + contents: read + copilot-requests: write + deployments: read + discussions: read + issues: read + models: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read + vulnerability-alerts: read network: allowed: - defaults diff --git a/.github/workflows/daily-file-diet.lock.yml b/.github/workflows/daily-file-diet.lock.yml index 03d449531e..97cadbebcf 100644 --- a/.github/workflows/daily-file-diet.lock.yml +++ b/.github/workflows/daily-file-diet.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"81e40b981d4ea14005d4722b0579399f1578cbff843c3d6b1d8834d690a0705b","body_hash":"9d964577fd8ced83461b9220bebbfb4f07bca1ffcc019e5ccb5cc4df4f123b36","compiler_version":"v0.79.8","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.60"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"c0338fef4749d08c21f8f975fb0e37efa17dda47","version":"v0.79.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]} -# This file was automatically generated by gh-aw (v0.79.8). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"adb3acc9d739374a6101b47a4675c29ebff3cb1e8bdd3735e0711d1f7e1fe6de","body_hash":"9d964577fd8ced83461b9220bebbfb4f07bca1ffcc019e5ccb5cc4df4f123b36","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} +# gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} +# This file was automatically generated by gh-aw (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -26,26 +26,29 @@ # Analyzes source files daily to identify oversized files that exceed healthy size thresholds, creating actionable refactoring issues # # Secrets used: -# - COPILOT_GITHUB_TOKEN # - GH_AW_GITHUB_MCP_SERVER_TOKEN # - GH_AW_GITHUB_TOKEN # - GITHUB_TOKEN # # Custom actions used: -# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 -# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 +# - actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 +# - actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 +# - actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 -# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 +# - actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 +# - github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 -# - ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa -# - ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c +# - ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d +# - ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d +# - ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be +# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b +# - ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 name: "Daily File Diet" on: @@ -78,6 +81,7 @@ jobs: contents: read env: GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: comment_id: "" comment_repo: "" @@ -87,7 +91,6 @@ jobs: engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} - secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} @@ -95,7 +98,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -105,8 +108,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Daily File Diet" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-file-diet.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info @@ -114,16 +117,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AGENT_VERSION: "1.0.60" - GH_AW_INFO_CLI_VERSION: "v0.79.8" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AGENT_VERSION: "1.0.65" + GH_AW_INFO_CLI_VERSION: "v0.81.6" GH_AW_INFO_WORKFLOW_NAME: "Daily File Diet" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -134,6 +137,30 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-dailyfilediet-${{ github.run_id }} + restore-keys: agentic-workflow-usage-dailyfilediet- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Restore daily AIC usage cache (artifact fallback) + id: restore-daily-aic-cache-fallback + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_RESTORE_DAILY_AIC_CACHE_HIT: ${{ steps.restore-daily-aic-cache.outputs.cache-hit }} + GH_AW_RESTORE_DAILY_AIC_CACHE_MATCHED_KEY: ${{ steps.restore-daily-aic-cache.outputs.cache-matched-key }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs'); + await main(); - name: Check daily workflow token guardrail id: daily-effective-workflow-guardrail if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} @@ -143,6 +170,8 @@ jobs: GH_AW_WORKFLOW_ID: "daily-file-diet" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} + GH_AW_HAS_SLASH_COMMAND: "false" + GH_AW_HAS_LABEL_COMMAND: "false" GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} with: @@ -152,13 +181,8 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs'); await main(); - - name: Validate COPILOT_GITHUB_TOKEN secret - id: validate-secret - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Checkout .github and .agents folders - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false sparse-checkout: | @@ -194,13 +218,16 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.79.8" + GH_AW_COMPILED_VERSION: "v0.81.6" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); await main(); + - name: Log runtime features + if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh" - name: Create prompt with built-in context env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt @@ -217,20 +244,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_33d86eef7c94e12f_EOF' + cat << 'GH_AW_PROMPT_3dad2b616068dd16_EOF' - GH_AW_PROMPT_33d86eef7c94e12f_EOF + GH_AW_PROMPT_3dad2b616068dd16_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_33d86eef7c94e12f_EOF' + cat << 'GH_AW_PROMPT_3dad2b616068dd16_EOF' Tools: create_issue, missing_tool, missing_data, noop - GH_AW_PROMPT_33d86eef7c94e12f_EOF + GH_AW_PROMPT_3dad2b616068dd16_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_33d86eef7c94e12f_EOF' + cat << 'GH_AW_PROMPT_3dad2b616068dd16_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -259,12 +286,12 @@ jobs: {{/if}} - GH_AW_PROMPT_33d86eef7c94e12f_EOF + GH_AW_PROMPT_3dad2b616068dd16_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_33d86eef7c94e12f_EOF' + cat << 'GH_AW_PROMPT_3dad2b616068dd16_EOF' {{#runtime-import .github/workflows/daily-file-diet.md}} - GH_AW_PROMPT_33d86eef7c94e12f_EOF + GH_AW_PROMPT_3dad2b616068dd16_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -328,7 +355,7 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: activation include-hidden-files: true @@ -351,6 +378,7 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + copilot-requests: write issues: read pull-requests: read concurrency: @@ -362,6 +390,7 @@ jobs: GH_AW_ASSETS_BRANCH: "" GH_AW_ASSETS_MAX_SIZE_KB: 0 GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: dailyfilediet outputs: agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} @@ -384,7 +413,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -393,8 +422,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Daily File Diet" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-file-diet.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -405,7 +434,7 @@ jobs: echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false - name: Create gh-aw temp directory @@ -416,17 +445,10 @@ jobs: GH_TOKEN: ${{ github.token }} - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Checkout PR branch id: checkout-pr if: | @@ -442,11 +464,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) @@ -458,7 +480,7 @@ jobs: const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: activation path: /tmp/gh-aw @@ -478,15 +500,15 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_08b5d9d8f487fb8b_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_23fff42fc8d88dcc_EOF' {"create_issue":{"expires":48,"labels":["agentic-workflows"],"max":1,"title_prefix":"[file-diet] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_08b5d9d8f487fb8b_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_23fff42fc8d88dcc_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -617,55 +639,17 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); await main(); - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - - name: Start MCP Gateway id: start-mcp-gateway env: + GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }} GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }} GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }} GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -eo pipefail mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" @@ -691,19 +675,19 @@ jobs: * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; esac DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.25' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --name awmg-mcpg --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.3.30' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_c6fee03c27b97257_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_f014db59cae17bc3_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.1.2", + "container": "ghcr.io/github/github-mcp-server:v1.4.0", "env": { - "GITHUB_HOST": "\${GITHUB_SERVER_URL}", - "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_HOST": "${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", "GITHUB_TOOLSETS": "context,repos,issues,pull_requests" }, @@ -715,10 +699,27 @@ jobs: } }, "safeoutputs": { - "type": "http", - "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + "type": "stdio", + "container": "ghcr.io/github/gh-aw-node", + "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"], + "args": ["-w", "\${GITHUB_WORKSPACE}"], + "entrypoint": "sh", + "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], + "env": { + "DEBUG": "*", + "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", + "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", + "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", + "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", + "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", + "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", + "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", + "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", + "GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}", + "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", + "GITHUB_TOKEN": "\${GITHUB_TOKEN}", + "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", + "RUNNER_TEMP": "\${RUNNER_TEMP}" }, "guard-policies": { "write-sink": { @@ -736,7 +737,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_c6fee03c27b97257_EOF + GH_AW_MCP_CONFIG_f014db59cae17bc3_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -794,38 +795,43 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + GH_AW_CHROOT_BINARIES_SOURCE_PATH=/tmp/gh-aw GH_AW_CHROOT_IDENTITY_HOME=/tmp/gh-aw/home node "${RUNNER_TEMP}/gh-aw/actions/patch_awf_chroot_config.cjs" fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(git:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(printf)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(safeoutputs:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(xargs)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(git:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(printf)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(safeoutputs:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(xargs)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -840,6 +846,8 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Detect agent errors if: always() id: detect-agent-errors @@ -847,17 +855,10 @@ jobs: run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Copy Copilot session state files to logs if: always() continue-on-error: true @@ -881,8 +882,7 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); await main(); env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -976,7 +976,7 @@ jobs: - name: Upload agent artifacts if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: agent path: | @@ -1016,6 +1016,8 @@ jobs: group: "gh-aw-conclusion-daily-file-diet" cancel-in-progress: false queue: max + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} @@ -1024,7 +1026,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1033,13 +1035,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Daily File Diet" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-file-diet.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1056,34 +1058,82 @@ jobs: run: | mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection echo "Usage artifact source file status:" - for file in /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do + for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file" done + [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true + [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl + mkdir -p /tmp/gh-aw/usage/activity + node ${{ runner.temp }}/gh-aw/actions/generate_usage_activity_summary.cjs find /tmp/gh-aw/usage -type f -print | sort - name: Upload usage artifact if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: usage path: | + /tmp/gh-aw/usage/aw_info.json /tmp/gh-aw/usage/aw-info.jsonl + /tmp/gh-aw/usage/agent_usage.json /tmp/gh-aw/usage/agent_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/github_rate_limits.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl + /tmp/gh-aw/usage/activity/summary.json if-no-files-found: ignore + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache-conclusion + if: always() + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-dailyfilediet-${{ github.run_id }} + restore-keys: agentic-workflow-usage-dailyfilediet- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Write daily AIC usage cache entry + id: write-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context); + const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs'); + await main(); + - name: Save daily AIC usage cache + id: save-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-dailyfilediet-${{ github.run_id }} + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Upload daily AIC usage cache artifact + id: upload-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: aic-usage-cache + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + if-no-files-found: ignore + retention-days: 7 - name: Process no-op messages id: noop uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1171,7 +1221,6 @@ jobs: GH_AW_WORKFLOW_ID: "daily-file-diet" GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" - GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} @@ -1206,11 +1255,13 @@ jobs: needs: - activation - agent - if: > - always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + if: always() && needs.agent.result != 'skipped' runs-on: ubuntu-latest permissions: contents: read + copilot-requests: write + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: aic: ${{ steps.parse_detection_token_usage.outputs.aic }} detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} @@ -1219,7 +1270,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1228,13 +1279,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Daily File Diet" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-file-diet.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1247,7 +1298,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Checkout repository for patch context if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false # --- Threat Detection --- @@ -1256,7 +1307,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d - name: Check if detection needed id: detection_guard if: always() @@ -1319,11 +1370,11 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1342,37 +1393,44 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + _GH_AW_CHROOT_JSON=$(jq -c --arg src /tmp/gh-aw --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home /tmp/gh-aw/home '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; } + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "/tmp/gh-aw/awf-config.json" fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1386,6 +1444,8 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Parse threat detection token usage for step summary id: parse_detection_token_usage if: always() @@ -1401,7 +1461,7 @@ jobs: await main(); - name: Upload threat detection log if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: detection path: /tmp/gh-aw/threat-detection/detection.log @@ -1441,6 +1501,8 @@ jobs: pre_activation: runs-on: ubuntu-slim + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' && steps.check_skip_if_match.outputs.skip_check_ok == 'true' }} matched_command: '' @@ -1450,15 +1512,15 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} env: GH_AW_SETUP_WORKFLOW_NAME: "Daily File Diet" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-file-diet.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Check team membership for workflow id: check_membership @@ -1507,7 +1569,8 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.60" + GH_AW_ENGINE_VERSION: "1.0.65" + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_TRACKER_ID: "daily-file-diet" GH_AW_WORKFLOW_ID: "daily-file-diet" @@ -1525,7 +1588,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1534,13 +1597,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Daily File Diet" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-file-diet.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1554,8 +1617,7 @@ jobs: - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash - # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. - run: | + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. GH_HOST="${GITHUB_SERVER_URL#https://}" @@ -1580,7 +1642,7 @@ jobs: await main(); - name: Upload Safe Outputs Items if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: safe-outputs-items path: | diff --git a/.github/workflows/daily-file-diet.md b/.github/workflows/daily-file-diet.md index 765120800d..6ff0451610 100644 --- a/.github/workflows/daily-file-diet.md +++ b/.github/workflows/daily-file-diet.md @@ -11,6 +11,7 @@ permissions: contents: read issues: read pull-requests: read + copilot-requests: write tracker-id: daily-file-diet diff --git a/.github/workflows/efficiency-improver.lock.yml b/.github/workflows/efficiency-improver.lock.yml index 9014349927..8b247ac720 100644 --- a/.github/workflows/efficiency-improver.lock.yml +++ b/.github/workflows/efficiency-improver.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"b6ae0c84aefd825f50f8448726f8a0f2e4f7892d138f42eace4b5946ea3a1daf","body_hash":"71a906a87a507ddbfaf81b4cc9292b0da283515520eb1ad3f347e091d91af2e5","compiler_version":"v0.79.8","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.60"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"c0338fef4749d08c21f8f975fb0e37efa17dda47","version":"v0.79.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]} -# This file was automatically generated by gh-aw (v0.79.8). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"78afcf7c1894d979c34194a405a64a8c8d334359d4ea3985ffaf67b9473db13f","body_hash":"71a906a87a507ddbfaf81b4cc9292b0da283515520eb1ad3f347e091d91af2e5","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} +# gh-aw-manifest: {"version":1,"secrets":["GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} +# This file was automatically generated by gh-aw (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -28,27 +28,31 @@ # computational footprint of the codebase. Always methodical, measurement-driven, and mindful of trade-offs. # # Secrets used: -# - COPILOT_GITHUB_TOKEN # - GH_AW_CI_TRIGGER_TOKEN # - GH_AW_GITHUB_MCP_SERVER_TOKEN # - GH_AW_GITHUB_TOKEN # - GITHUB_TOKEN # # Custom actions used: -# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 +# - actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 +# - actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 +# - actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 -# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 +# - actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 +# - github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 -# - ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa -# - ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c +# - ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d +# - ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d +# - ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be +# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b +# - ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 name: "Efficiency Improver" on: @@ -84,6 +88,7 @@ jobs: contents: read env: GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: comment_id: "" comment_repo: "" @@ -93,7 +98,6 @@ jobs: engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} - secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} @@ -101,7 +105,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -111,8 +115,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Efficiency Improver" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/efficiency-improver.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info @@ -120,16 +124,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AGENT_VERSION: "1.0.60" - GH_AW_INFO_CLI_VERSION: "v0.79.8" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AGENT_VERSION: "1.0.65" + GH_AW_INFO_CLI_VERSION: "v0.81.6" GH_AW_INFO_WORKFLOW_NAME: "Efficiency Improver" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","dotnet"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -140,6 +144,30 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-efficiencyimprover-${{ github.run_id }} + restore-keys: agentic-workflow-usage-efficiencyimprover- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Restore daily AIC usage cache (artifact fallback) + id: restore-daily-aic-cache-fallback + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_RESTORE_DAILY_AIC_CACHE_HIT: ${{ steps.restore-daily-aic-cache.outputs.cache-hit }} + GH_AW_RESTORE_DAILY_AIC_CACHE_MATCHED_KEY: ${{ steps.restore-daily-aic-cache.outputs.cache-matched-key }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs'); + await main(); - name: Check daily workflow token guardrail id: daily-effective-workflow-guardrail if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} @@ -149,6 +177,8 @@ jobs: GH_AW_WORKFLOW_ID: "efficiency-improver" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} + GH_AW_HAS_SLASH_COMMAND: "false" + GH_AW_HAS_LABEL_COMMAND: "false" GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} with: @@ -171,13 +201,8 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/add_reaction.cjs'); await main(); - - name: Validate COPILOT_GITHUB_TOKEN secret - id: validate-secret - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Checkout .github and .agents folders - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false sparse-checkout: | @@ -213,13 +238,16 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.79.8" + GH_AW_COMPILED_VERSION: "v0.81.6" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); await main(); + - name: Log runtime features + if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh" - name: Create prompt with built-in context env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt @@ -238,25 +266,25 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_d7fbbdf23d10e088_EOF' + cat << 'GH_AW_PROMPT_ed9c68fc9539c9df_EOF' - GH_AW_PROMPT_d7fbbdf23d10e088_EOF + GH_AW_PROMPT_ed9c68fc9539c9df_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/repo_memory_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_d7fbbdf23d10e088_EOF' + cat << 'GH_AW_PROMPT_ed9c68fc9539c9df_EOF' Tools: add_comment(max:10), create_issue(max:4), update_issue, create_pull_request(max:3), push_to_pull_request_branch, missing_tool, missing_data, noop - GH_AW_PROMPT_d7fbbdf23d10e088_EOF + GH_AW_PROMPT_ed9c68fc9539c9df_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md" - cat << 'GH_AW_PROMPT_d7fbbdf23d10e088_EOF' + cat << 'GH_AW_PROMPT_ed9c68fc9539c9df_EOF' - GH_AW_PROMPT_d7fbbdf23d10e088_EOF + GH_AW_PROMPT_ed9c68fc9539c9df_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_d7fbbdf23d10e088_EOF' + cat << 'GH_AW_PROMPT_ed9c68fc9539c9df_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -285,12 +313,12 @@ jobs: {{/if}} - GH_AW_PROMPT_d7fbbdf23d10e088_EOF + GH_AW_PROMPT_ed9c68fc9539c9df_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_d7fbbdf23d10e088_EOF' + cat << 'GH_AW_PROMPT_ed9c68fc9539c9df_EOF' {{#runtime-import .github/workflows/efficiency-improver.md}} - GH_AW_PROMPT_d7fbbdf23d10e088_EOF + GH_AW_PROMPT_ed9c68fc9539c9df_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -369,7 +397,7 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: activation include-hidden-files: true @@ -390,7 +418,23 @@ jobs: needs: activation if: needs.activation.outputs.daily_ai_credits_exceeded != 'true' runs-on: ubuntu-latest - permissions: read-all + permissions: + actions: read + attestations: read + checks: read + contents: read + copilot-requests: write + deployments: read + discussions: read + issues: read + models: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read + vulnerability-alerts: read concurrency: group: "gh-aw-copilot-${{ github.workflow }}" queue: max @@ -400,6 +444,7 @@ jobs: GH_AW_ASSETS_BRANCH: "" GH_AW_ASSETS_MAX_SIZE_KB: 0 GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: efficiencyimprover outputs: agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} @@ -422,7 +467,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -431,8 +476,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Efficiency Improver" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/efficiency-improver.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -443,7 +488,7 @@ jobs: echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false - name: Create gh-aw temp directory @@ -464,17 +509,10 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/clone_repo_memory_branch.sh" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Checkout PR branch id: checkout-pr if: | @@ -490,11 +528,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) @@ -506,7 +544,7 @@ jobs: const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: activation path: /tmp/gh-aw @@ -526,15 +564,15 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_7fb0a0132d0420c4_EOF' - {"add_comment":{"hide_older_comments":true,"max":10,"target":"*"},"create_issue":{"labels":["Area: Performance","agentic-workflows"],"max":4,"title_prefix":"[efficiency-improver] "},"create_pull_request":{"draft":true,"labels":["Area: Performance","agentic-workflows"],"max":3,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"request_review","title_prefix":"[efficiency-improver] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":102400,"max_patch_size":10240}]},"push_to_pull_request_branch":{"if_no_changes":"warn","max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"target":"*","title_prefix":"[efficiency-improver] "},"report_incomplete":{},"update_issue":{"allow_body":true,"max":1,"target":"*"}} - GH_AW_SAFE_OUTPUTS_CONFIG_7fb0a0132d0420c4_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_4699c1cac697011a_EOF' + {"add_comment":{"hide_older_comments":true,"max":10,"target":"*"},"create_issue":{"labels":["Area: Performance","agentic-workflows"],"max":4,"title_prefix":"[efficiency-improver] "},"create_pull_request":{"draft":true,"labels":["Area: Performance","agentic-workflows"],"max":3,"max_patch_files":100,"max_patch_size":4096,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"request_review","title_prefix":"[efficiency-improver] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":102400,"max_patch_size":10240}]},"push_to_pull_request_branch":{"if_no_changes":"warn","max_patch_size":4096,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"target":"*","title_prefix":"[efficiency-improver] "},"report_incomplete":{},"update_issue":{"allow_body":true,"max":1,"target":"*"}} + GH_AW_SAFE_OUTPUTS_CONFIG_4699c1cac697011a_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -761,10 +799,7 @@ jobs: "issueOrPRNumber": true }, "labels": { - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 + "type": "array" }, "milestone": { "optionalPositiveInteger": true @@ -795,7 +830,7 @@ jobs: "maxLength": 128 } }, - "customValidation": "requiresOneOf:status,title,body" + "customValidation": "requiresOneOf:status,title,body,labels,assignees,milestone" } } uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -805,55 +840,17 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); await main(); - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - - name: Start MCP Gateway id: start-mcp-gateway env: + GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }} GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }} GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }} GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -eo pipefail mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" @@ -879,19 +876,19 @@ jobs: * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; esac DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.25' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --name awmg-mcpg --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.3.30' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_b0e14880d3ca15bc_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_3fe8bc649bdd1bc2_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.1.2", + "container": "ghcr.io/github/github-mcp-server:v1.4.0", "env": { - "GITHUB_HOST": "\${GITHUB_SERVER_URL}", - "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_HOST": "${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", "GITHUB_TOOLSETS": "all" }, @@ -903,10 +900,27 @@ jobs: } }, "safeoutputs": { - "type": "http", - "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + "type": "stdio", + "container": "ghcr.io/github/gh-aw-node", + "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"], + "args": ["-w", "\${GITHUB_WORKSPACE}"], + "entrypoint": "sh", + "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], + "env": { + "DEBUG": "*", + "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", + "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", + "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", + "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", + "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", + "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", + "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", + "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", + "GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}", + "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", + "GITHUB_TOKEN": "\${GITHUB_TOKEN}", + "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", + "RUNNER_TEMP": "\${RUNNER_TEMP}" }, "guard-policies": { "write-sink": { @@ -924,7 +938,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_b0e14880d3ca15bc_EOF + GH_AW_MCP_CONFIG_3fe8bc649bdd1bc2_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -963,37 +977,41 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json","network":{"allowDomains":["*.vsblob.vsassets.io","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.nuget.org","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","azuresearch-usnc.nuget.org","azuresearch-ussc.nuget.org","builds.dotnet.microsoft.com","ci.dot.net","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","dc.services.visualstudio.com","dist.nuget.org","dot.net","dotnet.microsoft.com","dotnetcli.blob.core.windows.net","github.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","nuget.org","nuget.pkg.github.com","nugetregistryv2prod.blob.core.windows.net","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","oneocsp.microsoft.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","pkgs.dev.azure.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com","www.microsoft.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxAiCredits":2000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5.3","gemini-pro","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"claude":["agent"],"codex":["agent"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"computer-use":["copilot/*computer-use*","google/*computer-use*","gemini/*computer-use*","openai/*computer-use*"],"copilot":["agent"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent"],"gemini-3-flash":["copilot/gemini-3*flash*","google/gemini-3*flash*","gemini/gemini-3*flash*"],"gemini-3-pro":["copilot/gemini-3*pro*","google/gemini-3*pro*","google/nano-banana*","gemini/gemini-3*pro*"],"gemini-3.1-flash":["copilot/gemini-3.1*flash*","google/gemini-3.1*flash*","gemini/gemini-3.1*flash*"],"gemini-3.1-pro":["copilot/gemini-3.1*pro*","google/gemini-3.1*pro*","gemini/gemini-3.1*pro*"],"gemini-3.5-flash":["copilot/gemini-3.5*flash*","google/gemini-3.5*flash*","gemini/gemini-3.5*flash*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"gpt-5.2":["copilot/gpt-5.2*","openai/gpt-5.2*"],"gpt-5.3":["copilot/gpt-5.3*","openai/gpt-5.3*"],"gpt-5.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"gpt-5.5":["copilot/gpt-5.5*","openai/gpt-5.5*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mai-code":["copilot/MAI-Code*","copilot/mai-code*","openai/MAI-Code*"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"nano-banana":["copilot/nano-banana*","google/nano-banana*","gemini/nano-banana*"],"opus":["copilot/*opus*","anthropic/*opus*"],"opusplan":["opus?effort=high"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"small-agent":["haiku","gpt-5-mini","gemini-flash"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4.5*","copilot/*sonnet-4.6*","copilot/*sonnet-4-5-*","anthropic/*sonnet-4-5-*","copilot/*sonnet-4-6*","anthropic/*sonnet-4-6*"],"summarization":["haiku","gpt-5-mini","gemini-flash-lite","mini"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json","network":{"allowDomains":["*.vsblob.vsassets.io","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.nuget.org","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","azuresearch-usnc.nuget.org","azuresearch-ussc.nuget.org","builds.dotnet.microsoft.com","ci.dot.net","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","dc.services.visualstudio.com","dist.nuget.org","dot.net","dotnet.microsoft.com","dotnetcli.blob.core.windows.net","github.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","nuget.org","nuget.pkg.github.com","nugetregistryv2prod.blob.core.windows.net","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","oneocsp.microsoft.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","pkgs.dev.azure.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com","www.microsoft.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxCacheMisses":5,"maxAiCredits":2000,"models":{"agent":["sonnet-6x","gpt-5.5","gpt-5.4","gpt-5.3","gemini-pro","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"claude":["agent"],"codex":["agent"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"computer-use":["copilot/*computer-use*","google/*computer-use*","gemini/*computer-use*","openai/*computer-use*"],"copilot":["agent"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent"],"gemini-3-flash":["copilot/gemini-3*flash*","google/gemini-3*flash*","gemini/gemini-3*flash*"],"gemini-3-pro":["copilot/gemini-3*pro*","google/gemini-3*pro*","google/nano-banana*","gemini/gemini-3*pro*"],"gemini-3.1-flash":["copilot/gemini-3.1*flash*","google/gemini-3.1*flash*","gemini/gemini-3.1*flash*"],"gemini-3.1-pro":["copilot/gemini-3.1*pro*","google/gemini-3.1*pro*","gemini/gemini-3.1*pro*"],"gemini-3.5-flash":["copilot/gemini-3.5*flash*","google/gemini-3.5*flash*","gemini/gemini-3.5*flash*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"gpt-5.1":["copilot/gpt-5.1*","openai/gpt-5.1*"],"gpt-5.2":["copilot/gpt-5.2*","openai/gpt-5.2*"],"gpt-5.3":["copilot/gpt-5.3*","openai/gpt-5.3*"],"gpt-5.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"gpt-5.5":["copilot/gpt-5.5*","openai/gpt-5.5*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"image-generation":["copilot/gpt-image*","openai/gpt-image*","openai/chatgpt-image*","copilot/gemini-*image*","google/gemini-*image*","gemini/gemini-*image*","google/imagen*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mai-code":["copilot/MAI-Code*","copilot/mai-code*","openai/MAI-Code*"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"nano-banana":["copilot/nano-banana*","google/nano-banana*","gemini/nano-banana*"],"opus":["copilot/*opus*","anthropic/*opus*"],"opusplan":["opus?effort=high"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"small-agent":["haiku","gpt-5-mini","gemini-flash"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4.5*","copilot/*sonnet-4.6*","copilot/*sonnet-4-5-*","anthropic/*sonnet-4-5-*","copilot/*sonnet-4-6*","anthropic/*sonnet-4-6*"],"summarization":["haiku","gpt-5-mini","gemini-flash-lite","mini"],"vision":["copilot/gemini-*image*","google/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + GH_AW_CHROOT_BINARIES_SOURCE_PATH=/tmp/gh-aw GH_AW_CHROOT_IDENTITY_HOME=/tmp/gh-aw/home node "${RUNNER_TEMP}/gh-aw/actions/patch_awf_chroot_config.cjs" fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 60 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1008,6 +1026,8 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Detect agent errors if: always() id: detect-agent-errors @@ -1015,17 +1035,10 @@ jobs: run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Copy Copilot session state files to logs if: always() continue-on-error: true @@ -1049,8 +1062,7 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); await main(); env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -1150,7 +1162,7 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/sanitize_repo_memory_filenames.sh" - name: Upload repo-memory artifact (default) if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: repo-memory-default path: /tmp/gh-aw/repo-memory/default @@ -1159,7 +1171,7 @@ jobs: - name: Upload agent artifacts if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: agent path: | @@ -1195,13 +1207,14 @@ jobs: runs-on: ubuntu-slim permissions: contents: write - discussions: write issues: write pull-requests: write concurrency: group: "gh-aw-conclusion-efficiency-improver" cancel-in-progress: false queue: max + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} @@ -1210,7 +1223,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1219,13 +1232,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Efficiency Improver" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/efficiency-improver.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1242,34 +1255,82 @@ jobs: run: | mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection echo "Usage artifact source file status:" - for file in /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do + for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file" done + [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true + [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl + mkdir -p /tmp/gh-aw/usage/activity + node ${{ runner.temp }}/gh-aw/actions/generate_usage_activity_summary.cjs find /tmp/gh-aw/usage -type f -print | sort - name: Upload usage artifact if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: usage path: | + /tmp/gh-aw/usage/aw_info.json /tmp/gh-aw/usage/aw-info.jsonl + /tmp/gh-aw/usage/agent_usage.json /tmp/gh-aw/usage/agent_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/github_rate_limits.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl + /tmp/gh-aw/usage/activity/summary.json if-no-files-found: ignore + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache-conclusion + if: always() + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-efficiencyimprover-${{ github.run_id }} + restore-keys: agentic-workflow-usage-efficiencyimprover- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Write daily AIC usage cache entry + id: write-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context); + const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs'); + await main(); + - name: Save daily AIC usage cache + id: save-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-efficiencyimprover-${{ github.run_id }} + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Upload daily AIC usage cache artifact + id: upload-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: aic-usage-cache + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + if-no-files-found: ignore + retention-days: 7 - name: Process no-op messages id: noop uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1352,7 +1413,6 @@ jobs: GH_AW_WORKFLOW_ID: "efficiency-improver" GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" - GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} @@ -1393,11 +1453,13 @@ jobs: needs: - activation - agent - if: > - always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + if: always() && needs.agent.result != 'skipped' runs-on: ubuntu-latest permissions: contents: read + copilot-requests: write + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: aic: ${{ steps.parse_detection_token_usage.outputs.aic }} detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} @@ -1406,7 +1468,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1415,13 +1477,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Efficiency Improver" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/efficiency-improver.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1434,7 +1496,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Checkout repository for patch context if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false # --- Threat Detection --- @@ -1443,7 +1505,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d - name: Check if detection needed id: detection_guard if: always() @@ -1506,11 +1568,11 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1529,37 +1591,44 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + _GH_AW_CHROOT_JSON=$(jq -c --arg src /tmp/gh-aw --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home /tmp/gh-aw/home '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; } + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "/tmp/gh-aw/awf-config.json" fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1573,6 +1642,8 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Parse threat detection token usage for step summary id: parse_detection_token_usage if: always() @@ -1588,7 +1659,7 @@ jobs: await main(); - name: Upload threat detection log if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: detection path: /tmp/gh-aw/threat-detection/detection.log @@ -1630,6 +1701,8 @@ jobs: runs-on: ubuntu-slim permissions: pull-requests: read + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} check_result: ${{ steps.check.outcome }} @@ -1640,15 +1713,15 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} env: GH_AW_SETUP_WORKFLOW_NAME: "Efficiency Improver" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/efficiency-improver.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Check team membership for workflow id: check_membership @@ -1693,6 +1766,8 @@ jobs: concurrency: group: "push-repo-memory-${{ github.repository }}|memory/efficiency-improver" cancel-in-progress: false + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: patch_size_exceeded_default: ${{ steps.push_repo_memory_default.outputs.patch_size_exceeded }} validation_error_default: ${{ steps.push_repo_memory_default.outputs.validation_error }} @@ -1700,7 +1775,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1709,27 +1784,20 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Efficiency Improver" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/efficiency-improver.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false sparse-checkout: . - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Download repo-memory artifact (default) uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 continue-on-error: true @@ -1768,7 +1836,6 @@ jobs: runs-on: ubuntu-slim permissions: contents: write - discussions: write issues: write pull-requests: write timeout-minutes: 45 @@ -1782,7 +1849,8 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.60" + GH_AW_ENGINE_VERSION: "1.0.65" + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "efficiency-improver" GH_AW_WORKFLOW_NAME: "Efficiency Improver" @@ -1805,7 +1873,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1814,13 +1882,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Efficiency Improver" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/efficiency-improver.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1833,55 +1901,27 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Download patch artifact continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ - - name: Extract base branch from agent output - id: extract-base-branch - if: steps.download-agent-output.outcome == 'success' - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/extract_base_branch_from_agent_output.cjs'); - await main(); - - name: Checkout repository (trusted default branch for comment events) - if: ((!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') || (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch')) && (github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - ref: ${{ github.event.repository.default_branch }} - token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - persist-credentials: false - fetch-depth: 1 - name: Checkout repository - if: ((!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') || (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch')) && github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') || (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch') + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: - ref: ${{ steps.extract-base-branch.outputs.base-branch || github.base_ref || github.event.pull_request.base.ref || github.ref_name || github.event.repository.default_branch }} + persist-credentials: true token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - persist-credentials: false - fetch-depth: 1 - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') || (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch') env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GIT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GIT_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash - # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. - run: | + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. GH_HOST="${GITHUB_SERVER_URL#https://}" @@ -1896,7 +1936,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":10,\"target\":\"*\"},\"create_issue\":{\"labels\":[\"Area: Performance\",\"agentic-workflows\"],\"max\":4,\"title_prefix\":\"[efficiency-improver] \"},\"create_pull_request\":{\"draft\":true,\"labels\":[\"Area: Performance\",\"agentic-workflows\"],\"max\":3,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"request_review\",\"title_prefix\":\"[efficiency-improver] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"target\":\"*\",\"title_prefix\":\"[efficiency-improver] \"},\"report_incomplete\":{},\"update_issue\":{\"allow_body\":true,\"max\":1,\"target\":\"*\"}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":10,\"target\":\"*\"},\"create_issue\":{\"labels\":[\"Area: Performance\",\"agentic-workflows\"],\"max\":4,\"title_prefix\":\"[efficiency-improver] \"},\"create_pull_request\":{\"draft\":true,\"labels\":[\"Area: Performance\",\"agentic-workflows\"],\"max\":3,\"max_patch_files\":100,\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"request_review\",\"title_prefix\":\"[efficiency-improver] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"target\":\"*\",\"title_prefix\":\"[efficiency-improver] \"},\"report_incomplete\":{},\"update_issue\":{\"allow_body\":true,\"max\":1,\"target\":\"*\"}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -1907,7 +1947,7 @@ jobs: await main(); - name: Upload Safe Outputs Items if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: safe-outputs-items path: | diff --git a/.github/workflows/efficiency-improver.md b/.github/workflows/efficiency-improver.md index 63a40e4d52..0f1ba68470 100644 --- a/.github/workflows/efficiency-improver.md +++ b/.github/workflows/efficiency-improver.md @@ -38,7 +38,23 @@ timeout-minutes: 60 max-ai-credits: 2000 -permissions: read-all +permissions: + actions: read + attestations: read + checks: read + contents: read + copilot-requests: write + deployments: read + discussions: read + issues: read + models: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read + vulnerability-alerts: read network: allowed: diff --git a/.github/workflows/http-link-checker.lock.yml b/.github/workflows/http-link-checker.lock.yml index e639aea978..75f96c932e 100644 --- a/.github/workflows/http-link-checker.lock.yml +++ b/.github/workflows/http-link-checker.lock.yml @@ -1,5 +1,7 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"cc96c0b21d2576cbfa38b8e8de118f20a71ccfbbbdd7c1428c73e5693825b1cd","body_hash":"23545c7bb8115e7ff61bc27e03120d18246f8dc3089b44192c08bf5e5c0f98f3","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"3ea13c02d765410340d533515cb31a7eef2baaf0","version":"v0.77.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.58"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.22"},{"image":"ghcr.io/github/github-mcp-server:v1.1.0"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"95dddb9a2aa681e7d962235952385b773c7b56ef20b0c2943f57e801bc7eadd0","body_hash":"23545c7bb8115e7ff61bc27e03120d18246f8dc3089b44192c08bf5e5c0f98f3","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} +# gh-aw-manifest: {"version":1,"secrets":["GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} +# This file was automatically generated by gh-aw (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -14,7 +16,6 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.77.5). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -25,7 +26,6 @@ # Weekly automated link checker that finds and fixes broken links in documentation files # # Secrets used: -# - COPILOT_GITHUB_TOKEN # - GH_AW_CI_TRIGGER_TOKEN # - GH_AW_GITHUB_MCP_SERVER_TOKEN # - GH_AW_GITHUB_TOKEN @@ -35,21 +35,21 @@ # - actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 # - actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 # - actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 -# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 -# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 +# - actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 -# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 +# - actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 +# - github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.25.58 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58 -# - ghcr.io/github/gh-aw-firewall/squid:0.25.58 -# - ghcr.io/github/gh-aw-mcpg:v0.3.22 -# - ghcr.io/github/github-mcp-server:v1.1.0 -# - node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14 +# - ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d +# - ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d +# - ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be +# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b +# - ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 name: "Weekly HTTP Link Checker & Fixer" on: @@ -77,13 +77,18 @@ jobs: permissions: actions: read contents: read + env: + GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: comment_id: "" comment_repo: "" + daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }} + daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }} + daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }} engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} - secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} @@ -91,15 +96,16 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} + safe-output-artifact-client: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} env: GH_AW_SETUP_WORKFLOW_NAME: "Weekly HTTP Link Checker & Fixer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/http-link-checker.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.55" - GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info @@ -107,16 +113,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.55" - GH_AW_INFO_AGENT_VERSION: "1.0.55" - GH_AW_INFO_CLI_VERSION: "v0.77.5" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AGENT_VERSION: "1.0.65" + GH_AW_INFO_CLI_VERSION: "v0.81.6" GH_AW_INFO_WORKFLOW_NAME: "Weekly HTTP Link Checker & Fixer" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["node","python","github"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -127,13 +133,52 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); - - name: Validate COPILOT_GITHUB_TOKEN secret - id: validate-secret - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-httplinkchecker-${{ github.run_id }} + restore-keys: agentic-workflow-usage-httplinkchecker- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Restore daily AIC usage cache (artifact fallback) + id: restore-daily-aic-cache-fallback + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_RESTORE_DAILY_AIC_CACHE_HIT: ${{ steps.restore-daily-aic-cache.outputs.cache-hit }} + GH_AW_RESTORE_DAILY_AIC_CACHE_MATCHED_KEY: ${{ steps.restore-daily-aic-cache.outputs.cache-matched-key }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs'); + await main(); + - name: Check daily workflow token guardrail + id: daily-effective-workflow-guardrail + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_WORKFLOW_NAME: "Weekly HTTP Link Checker & Fixer" + GH_AW_WORKFLOW_ID: "http-link-checker" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} + GH_AW_HAS_SLASH_COMMAND: "false" + GH_AW_HAS_LABEL_COMMAND: "false" + GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs'); + await main(); - name: Checkout .github and .agents folders - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false sparse-checkout: | @@ -169,13 +214,16 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.77.5" + GH_AW_COMPILED_VERSION: "v0.81.6" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); await main(); + - name: Log runtime features + if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh" - name: Create prompt with built-in context env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt @@ -192,24 +240,24 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_b04855d0a3340a75_EOF' + cat << 'GH_AW_PROMPT_42cc0ed418a653ec_EOF' - GH_AW_PROMPT_b04855d0a3340a75_EOF + GH_AW_PROMPT_42cc0ed418a653ec_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_b04855d0a3340a75_EOF' + cat << 'GH_AW_PROMPT_42cc0ed418a653ec_EOF' Tools: create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_b04855d0a3340a75_EOF + GH_AW_PROMPT_42cc0ed418a653ec_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_b04855d0a3340a75_EOF' + cat << 'GH_AW_PROMPT_42cc0ed418a653ec_EOF' - GH_AW_PROMPT_b04855d0a3340a75_EOF + GH_AW_PROMPT_42cc0ed418a653ec_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_b04855d0a3340a75_EOF' + cat << 'GH_AW_PROMPT_42cc0ed418a653ec_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -238,12 +286,12 @@ jobs: {{/if}} - GH_AW_PROMPT_b04855d0a3340a75_EOF + GH_AW_PROMPT_42cc0ed418a653ec_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_b04855d0a3340a75_EOF' + cat << 'GH_AW_PROMPT_42cc0ed418a653ec_EOF' {{#runtime-import .github/workflows/http-link-checker.md}} - GH_AW_PROMPT_b04855d0a3340a75_EOF + GH_AW_PROMPT_42cc0ed418a653ec_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -310,13 +358,13 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: activation include-hidden-files: true path: | /tmp/gh-aw/aw_info.json - /tmp/gh-aw/model_multipliers.json + /tmp/gh-aw/models.json /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/aw-prompts/prompt-template.txt /tmp/gh-aw/aw-prompts/prompt-import-tree.json @@ -329,8 +377,25 @@ jobs: agent: needs: activation + if: needs.activation.outputs.daily_ai_credits_exceeded != 'true' runs-on: ubuntu-latest - permissions: read-all + permissions: + actions: read + attestations: read + checks: read + contents: read + copilot-requests: write + deployments: read + discussions: read + issues: read + models: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read + vulnerability-alerts: read concurrency: group: "gh-aw-copilot-${{ github.workflow }}" queue: max @@ -340,12 +405,17 @@ jobs: GH_AW_ASSETS_BRANCH: "" GH_AW_ASSETS_MAX_SIZE_KB: 0 GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: httplinkchecker outputs: agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} + ai_credits_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.ai_credits_rate_limit_error || 'false' }} + aic: ${{ steps.parse-mcp-gateway.outputs.aic }} + ambient_context: ${{ steps.parse-mcp-gateway.outputs.ambient_context }} + cache_memory_restore_0_cache_hit: ${{ steps.restore_cache_memory_0.outputs.cache-hit || 'false' }} + cache_memory_restore_0_matched_key: ${{ steps.restore_cache_memory_0.outputs.cache-matched-key || '' }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} - effective_tokens_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.effective_tokens_rate_limit_error || 'false' }} has_patch: ${{ steps.collect_output.outputs.has_patch }} inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} @@ -356,10 +426,11 @@ jobs: setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} + unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -368,8 +439,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Weekly HTTP Link Checker & Fixer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/http-link-checker.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.55" - GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -399,6 +470,7 @@ jobs: - name: Create cache-memory directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_cache_memory_dir.sh" - name: Restore cache-memory file share data + id: restore_cache_memory_0 uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: key: memory-none-nopolicy-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }} @@ -412,21 +484,14 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/setup_cache_memory_git.sh" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Checkout PR branch id: checkout-pr if: | - github.event.pull_request || github.event.issue.pull_request + github.event.pull_request || github.event.issue.pull_request || github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -438,11 +503,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.55 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.58 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) @@ -454,7 +519,7 @@ jobs: const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: activation path: /tmp/gh-aw @@ -474,15 +539,15 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.58 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58 ghcr.io/github/gh-aw-firewall/squid:0.25.58 ghcr.io/github/gh-aw-mcpg:v0.3.22 ghcr.io/github/github-mcp-server:v1.1.0 node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_085c681009b111a2_EOF' - {"create_pull_request":{"draft":false,"if_no_changes":"warn","labels":["Area: Documentation","agentic-workflows"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"fallback-to-issue","title_prefix":"[link-checker] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_085c681009b111a2_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_2ff4cda3c1a98714_EOF' + {"create_pull_request":{"draft":false,"if_no_changes":"warn","labels":["Area: Documentation","agentic-workflows"],"max":1,"max_patch_files":100,"max_patch_size":4096,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"fallback-to-issue","title_prefix":"[link-checker] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_2ff4cda3c1a98714_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -617,55 +682,17 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); await main(); - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - - name: Start MCP Gateway id: start-mcp-gateway env: + GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }} GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }} GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }} GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -eo pipefail mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" @@ -691,19 +718,19 @@ jobs: * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; esac DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.22' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --name awmg-mcpg --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.3.30' - mkdir -p /home/runner/.copilot + mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_2a4afb1d39e4ac67_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_f014db59cae17bc3_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.1.0", + "container": "ghcr.io/github/github-mcp-server:v1.4.0", "env": { - "GITHUB_HOST": "\${GITHUB_SERVER_URL}", - "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_HOST": "${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", "GITHUB_TOOLSETS": "context,repos,issues,pull_requests" }, @@ -715,10 +742,27 @@ jobs: } }, "safeoutputs": { - "type": "http", - "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + "type": "stdio", + "container": "ghcr.io/github/gh-aw-node", + "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"], + "args": ["-w", "\${GITHUB_WORKSPACE}"], + "entrypoint": "sh", + "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], + "env": { + "DEBUG": "*", + "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", + "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", + "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", + "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", + "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", + "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", + "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", + "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", + "GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}", + "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", + "GITHUB_TOKEN": "\${GITHUB_TOKEN}", + "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", + "RUNNER_TEMP": "\${RUNNER_TEMP}" }, "guard-policies": { "write-sink": { @@ -736,7 +780,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_2a4afb1d39e4ac67_EOF + GH_AW_MCP_CONFIG_f014db59cae17bc3_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -765,41 +809,53 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + mkdir -p "$HOME/.copilot" + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" + export XDG_CONFIG_HOME="$HOME" + export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json" touch /tmp/gh-aw/agent-step-summary.md GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.58/awf-config.schema.json","network":{"allowDomains":["*.githubusercontent.com","*.pythonhosted.org","anaconda.org","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.npms.io","binstar.org","bootstrap.pypa.io","bun.sh","cdn.jsdelivr.net","codeload.github.com","conda.anaconda.org","conda.binstar.org","deb.nodesource.com","deno.land","docs.github.com","esm.sh","files.pythonhosted.org","get.pnpm.io","github-cloud.githubusercontent.com","github-cloud.s3.amazonaws.com","github.blog","github.com","github.githubassets.com","googleapis.deno.dev","googlechromelabs.github.io","host.docker.internal","jsr.io","lfs.github.com","nodejs.org","npm.pkg.github.com","npmjs.com","npmjs.org","objects.githubusercontent.com","patch-diff.githubusercontent.com","pip.pypa.io","pypi.org","pypi.python.org","raw.githubusercontent.com","registry.bower.io","registry.npmjs.com","registry.npmjs.org","registry.yarnpkg.com","repo.anaconda.com","repo.continuum.io","repo.yarnpkg.com","skimdb.npmjs.com","storage.googleapis.com","telemetry.enterprise.githubcopilot.com","telemetry.vercel.com","www.npmjs.com","www.npmjs.org","yarnpkg.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5.3","gemini-pro","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"claude":["agent"],"codex":["agent"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"computer-use":["copilot/*computer-use*","google/*computer-use*","gemini/*computer-use*","openai/*computer-use*"],"copilot":["agent"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent"],"gemini-3-flash":["copilot/gemini-3*flash*","google/gemini-3*flash*","gemini/gemini-3*flash*"],"gemini-3-pro":["copilot/gemini-3*pro*","google/gemini-3*pro*","gemini/gemini-3*pro*"],"gemini-3.1-flash":["copilot/gemini-3.1*flash*","google/gemini-3.1*flash*","gemini/gemini-3.1*flash*"],"gemini-3.1-pro":["copilot/gemini-3.1*pro*","google/gemini-3.1*pro*","gemini/gemini-3.1*pro*"],"gemini-3.5-flash":["copilot/gemini-3.5*flash*","google/gemini-3.5*flash*","gemini/gemini-3.5*flash*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"gpt-5.2":["copilot/gpt-5.2*","openai/gpt-5.2*"],"gpt-5.3":["copilot/gpt-5.3*","openai/gpt-5.3*"],"gpt-5.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"gpt-5.5":["copilot/gpt-5.5*","openai/gpt-5.5*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"opus":["copilot/*opus*","anthropic/*opus*"],"opusplan":["opus?effort=high"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4-5-*","anthropic/*sonnet-4-5-*","copilot/*sonnet-4-6*","anthropic/*sonnet-4-6*"],"summarization":["haiku","gpt-5-mini","gemini-flash-lite","mini"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.58"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" - GH_AW_MODEL_MULTIPLIERS_PATH="/tmp/gh-aw/model_multipliers.json" node "${RUNNER_TEMP}/gh-aw/actions/merge_awf_model_multipliers.cjs" + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.githubusercontent.com\",\"*.pythonhosted.org\",\"anaconda.org\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.npms.io\",\"binstar.org\",\"bootstrap.pypa.io\",\"bun.sh\",\"cdn.jsdelivr.net\",\"codeload.github.com\",\"conda.anaconda.org\",\"conda.binstar.org\",\"deb.nodesource.com\",\"deno.land\",\"docs.github.com\",\"esm.sh\",\"files.pythonhosted.org\",\"get.pnpm.io\",\"github-cloud.githubusercontent.com\",\"github-cloud.s3.amazonaws.com\",\"github.blog\",\"github.com\",\"github.githubassets.com\",\"googleapis.deno.dev\",\"googlechromelabs.github.io\",\"host.docker.internal\",\"jsr.io\",\"lfs.github.com\",\"nodejs.org\",\"npm.pkg.github.com\",\"npmjs.com\",\"npmjs.org\",\"objects.githubusercontent.com\",\"patch-diff.githubusercontent.com\",\"pip.pypa.io\",\"pypi.org\",\"pypi.python.org\",\"raw.githubusercontent.com\",\"registry.bower.io\",\"registry.npmjs.com\",\"registry.npmjs.org\",\"registry.yarnpkg.com\",\"repo.anaconda.com\",\"repo.continuum.io\",\"repo.yarnpkg.com\",\"skimdb.npmjs.com\",\"storage.googleapis.com\",\"telemetry.enterprise.githubcopilot.com\",\"telemetry.vercel.com\",\"www.npmjs.com\",\"www.npmjs.org\",\"yarnpkg.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + GH_AW_CHROOT_BINARIES_SOURCE_PATH=/tmp/gh-aw GH_AW_CHROOT_IDENTITY_HOME=/tmp/gh-aw/home node "${RUNNER_TEMP}/gh-aw/actions/patch_awf_chroot_config.cjs" fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir /tmp/gh-aw/cache-memory/ --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir /tmp/gh-aw/cache-memory/ --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} + GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.77.5 + GH_AW_TIMEOUT_MINUTES: 60 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -814,7 +870,8 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} - XDG_CONFIG_HOME: /home/runner + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Detect agent errors if: always() id: detect-agent-errors @@ -822,17 +879,10 @@ jobs: run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Copy Copilot session state files to logs if: always() continue-on-error: true @@ -856,8 +906,7 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); await main(); env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -960,7 +1009,7 @@ jobs: GH_AW_CACHE_DIR: /tmp/gh-aw/cache-memory run: bash "${RUNNER_TEMP}/gh-aw/actions/check_cache_memory_git_integrity.sh" - name: Upload cache-memory data as artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 if: always() with: name: cache-memory @@ -969,7 +1018,7 @@ jobs: - name: Upload agent artifacts if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: agent path: | @@ -1001,7 +1050,7 @@ jobs: - update_cache_memory if: > always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || - needs.activation.outputs.stale_lock_file_failed == 'true') + needs.activation.outputs.stale_lock_file_failed == 'true' || needs.activation.outputs.daily_ai_credits_exceeded == 'true') runs-on: ubuntu-slim permissions: contents: write @@ -1011,6 +1060,8 @@ jobs: group: "gh-aw-conclusion-http-link-checker" cancel-in-progress: false queue: max + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} @@ -1019,7 +1070,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1028,13 +1079,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Weekly HTTP Link Checker & Fixer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/http-link-checker.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.55" - GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1045,6 +1096,88 @@ jobs: mkdir -p /tmp/gh-aw/ find "/tmp/gh-aw/" -type f -print echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Collect usage artifact files + if: always() + continue-on-error: true + run: | + mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection + echo "Usage artifact source file status:" + for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do + [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file" + done + [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true + [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true + [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true + [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true + [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true + [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl + [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl + mkdir -p /tmp/gh-aw/usage/activity + node ${{ runner.temp }}/gh-aw/actions/generate_usage_activity_summary.cjs + find /tmp/gh-aw/usage -type f -print | sort + - name: Upload usage artifact + if: always() + continue-on-error: true + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: usage + path: | + /tmp/gh-aw/usage/aw_info.json + /tmp/gh-aw/usage/aw-info.jsonl + /tmp/gh-aw/usage/agent_usage.json + /tmp/gh-aw/usage/agent_usage.jsonl + /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/github_rate_limits.jsonl + /tmp/gh-aw/usage/agent/token_usage.jsonl + /tmp/gh-aw/usage/detection/token_usage.jsonl + /tmp/gh-aw/usage/activity/summary.json + if-no-files-found: ignore + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache-conclusion + if: always() + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-httplinkchecker-${{ github.run_id }} + restore-keys: agentic-workflow-usage-httplinkchecker- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Write daily AIC usage cache entry + id: write-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context); + const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs'); + await main(); + - name: Save daily AIC usage cache + id: save-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-httplinkchecker-${{ github.run_id }} + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Upload daily AIC usage cache artifact + id: upload-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: aic-usage-cache + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + if-no-files-found: ignore + retention-days: 7 - name: Process no-op messages id: noop uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1056,6 +1189,10 @@ jobs: GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_NOOP_REPORT_AS_ISSUE: "true" + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} + GH_AW_WORKFLOW_ID: "http-link-checker" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1123,10 +1260,13 @@ jobs: GH_AW_WORKFLOW_ID: "http-link-checker" GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" - GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} - GH_AW_EFFECTIVE_TOKENS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.effective_tokens_rate_limit_error || 'false' }} + GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} + GH_AW_UNKNOWN_MODEL_AI_CREDITS: ${{ needs.agent.outputs.unknown_model_ai_credits || 'false' }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} @@ -1136,13 +1276,17 @@ jobs: GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }} GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_DAILY_AI_CREDITS_EXCEEDED: ${{ needs.activation.outputs.daily_ai_credits_exceeded }} + GH_AW_DAILY_AI_CREDITS_TOTAL_EFFECTIVE_TOKENS: ${{ needs.activation.outputs.daily_ai_credits_total_effective_tokens }} + GH_AW_DAILY_AI_CREDITS_THRESHOLD: ${{ needs.activation.outputs.daily_ai_credits_threshold }} GH_AW_GROUP_REPORTS: "false" GH_AW_FAILURE_REPORT_AS_ISSUE: "true" GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" GH_AW_TIMEOUT_MINUTES: "60" - GH_AW_MAX_EFFECTIVE_TOKENS: "25000000" GH_AW_CACHE_MEMORY_ENABLED: "true" + GH_AW_CACHE_MEMORY_RESTORE_0_MATCHED_KEY: ${{ needs.agent.outputs.cache_memory_restore_0_matched_key || '' }} + GH_AW_CACHE_MEMORY_RESTORE_0_CACHE_HIT: ${{ needs.agent.outputs.cache_memory_restore_0_cache_hit || 'false' }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1155,19 +1299,22 @@ jobs: needs: - activation - agent - if: > - always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + if: always() && needs.agent.result != 'skipped' runs-on: ubuntu-latest permissions: contents: read + copilot-requests: write + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: + aic: ${{ steps.parse_detection_token_usage.outputs.aic }} detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} detection_reason: ${{ steps.detection_conclusion.outputs.reason }} detection_success: ${{ steps.detection_conclusion.outputs.success }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1176,13 +1323,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Weekly HTTP Link Checker & Fixer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/http-link-checker.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.55" - GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1195,7 +1342,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Checkout repository for patch context if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false # --- Threat Detection --- @@ -1204,7 +1351,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.58 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58 ghcr.io/github/gh-aw-firewall/squid:0.25.58 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d - name: Check if detection needed id: detection_guard if: always() @@ -1223,12 +1370,13 @@ jobs: if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" - rm -f /home/runner/.copilot/mcp-config.json + rm -f "$HOME/.copilot/mcp-config.json" rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" - name: Prepare threat detection files if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + rm -f /tmp/gh-aw/agent_usage.json cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context." @@ -1266,11 +1414,11 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.55 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.58 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1280,39 +1428,53 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + mkdir -p "$HOME/.copilot" + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" + export XDG_CONFIG_HOME="$HOME" touch /tmp/gh-aw/agent-step-summary.md GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.58/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","registry.npmjs.org","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000},"container":{"imageTag":"0.25.58"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" - GH_AW_MODEL_MULTIPLIERS_PATH="/tmp/gh-aw/model_multipliers.json" node "${RUNNER_TEMP}/gh-aw/actions/merge_awf_model_multipliers.cjs" + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + _GH_AW_CHROOT_JSON=$(jq -c --arg src /tmp/gh-aw --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home /tmp/gh-aw/home '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; } + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "/tmp/gh-aw/awf-config.json" fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }} + GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.77.5 + GH_AW_TIMEOUT_MINUTES: 20 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1326,10 +1488,24 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} - XDG_CONFIG_HOME: /home/runner + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} + - name: Parse threat detection token usage for step summary + id: parse_detection_token_usage + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); - name: Upload threat detection log if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: detection path: /tmp/gh-aw/threat-detection/detection.log @@ -1378,15 +1554,20 @@ jobs: contents: write issues: write pull-requests: write - timeout-minutes: 15 + timeout-minutes: 45 env: + GH_AW_AGENT_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/http-link-checker" GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.55" + GH_AW_ENGINE_VERSION: "1.0.65" + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "http-link-checker" GH_AW_WORKFLOW_NAME: "Weekly HTTP Link Checker & Fixer" GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/http-link-checker.md" @@ -1402,7 +1583,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1411,13 +1592,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Weekly HTTP Link Checker & Fixer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/http-link-checker.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.55" - GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1430,54 +1611,27 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Download patch artifact continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ - - name: Extract base branch from agent output - id: extract-base-branch - if: steps.download-agent-output.outcome == 'success' - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/extract_base_branch_from_agent_output.cjs'); - await main(); - - name: Checkout repository (trusted default branch for comment events) - if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') && (github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - ref: ${{ github.event.repository.default_branch }} - token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - persist-credentials: false - fetch-depth: 1 - name: Checkout repository - if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') && github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: - ref: ${{ steps.extract-base-branch.outputs.base-branch || github.base_ref || github.event.pull_request.base.ref || github.ref_name || github.event.repository.default_branch }} + persist-credentials: true token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - persist-credentials: false - fetch-depth: 1 - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GIT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GIT_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash - run: | + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. GH_HOST="${GITHUB_SERVER_URL#https://}" @@ -1492,7 +1646,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.npms.io,binstar.org,bootstrap.pypa.io,bun.sh,cdn.jsdelivr.net,codeload.github.com,conda.anaconda.org,conda.binstar.org,deb.nodesource.com,deno.land,docs.github.com,esm.sh,files.pythonhosted.org,get.pnpm.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,googleapis.deno.dev,googlechromelabs.github.io,host.docker.internal,jsr.io,lfs.github.com,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,objects.githubusercontent.com,patch-diff.githubusercontent.com,pip.pypa.io,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.bower.io,registry.npmjs.com,registry.npmjs.org,registry.yarnpkg.com,repo.anaconda.com,repo.continuum.io,repo.yarnpkg.com,skimdb.npmjs.com,storage.googleapis.com,telemetry.enterprise.githubcopilot.com,telemetry.vercel.com,www.npmjs.com,www.npmjs.org,yarnpkg.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"draft\":false,\"if_no_changes\":\"warn\",\"labels\":[\"Area: Documentation\",\"agentic-workflows\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"fallback-to-issue\",\"title_prefix\":\"[link-checker] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"draft\":false,\"if_no_changes\":\"warn\",\"labels\":[\"Area: Documentation\",\"agentic-workflows\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"fallback-to-issue\",\"title_prefix\":\"[link-checker] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -1503,7 +1657,7 @@ jobs: await main(); - name: Upload Safe Outputs Items if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: safe-outputs-items path: | @@ -1520,11 +1674,12 @@ jobs: runs-on: ubuntu-slim permissions: {} env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: httplinkchecker steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1533,12 +1688,12 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Weekly HTTP Link Checker & Fixer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/http-link-checker.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.55" - GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download cache-memory artifact (default) id: download_cache_default - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 continue-on-error: true with: name: cache-memory diff --git a/.github/workflows/http-link-checker.md b/.github/workflows/http-link-checker.md index 18ba416872..aa0765cc50 100644 --- a/.github/workflows/http-link-checker.md +++ b/.github/workflows/http-link-checker.md @@ -2,7 +2,23 @@ description: Weekly automated link checker that finds and fixes broken links in documentation files on: schedule: weekly on Friday -permissions: read-all +permissions: + actions: read + attestations: read + checks: read + contents: read + copilot-requests: write + deployments: read + discussions: read + issues: read + models: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read + vulnerability-alerts: read timeout-minutes: 60 network: allowed: diff --git a/.github/workflows/issue-repro-triage.lock.yml b/.github/workflows/issue-repro-triage.lock.yml index 8a6d905419..e73940fcf5 100644 --- a/.github/workflows/issue-repro-triage.lock.yml +++ b/.github/workflows/issue-repro-triage.lock.yml @@ -1,5 +1,7 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"5cbb0dc0f34bb50876a362973479fbec70f374d8a6c68521348599869b2ff670","compiler_version":"v0.72.1","strict":true,"agent_id":"copilot"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"bc56a0cad2f450c562810785ef38649c04db812a","version":"v0.72.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"08c304e55893c7e2a8b1fa8bbc2215a8ba47c9676acf58dde3820a9143fe51a8","body_hash":"d5a23a31072f187d7eaaed0c2175ea008ca9603597b7b043443aedee11461e98","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} +# gh-aw-manifest: {"version":1,"secrets":["APP_PRIVATE_KEY","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/create-github-app-token","sha":"bcd2ba49218906704ab6c1aa796996da409d3eb1","version":"v3.2.0"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} +# This file was automatically generated by gh-aw (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -14,7 +16,6 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.72.1). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -29,7 +30,7 @@ # - shared/repo-build-setup.md # # Secrets used: -# - COPILOT_GITHUB_TOKEN +# - APP_PRIVATE_KEY # - GH_AW_CI_TRIGGER_TOKEN # - GH_AW_GITHUB_MCP_SERVER_TOKEN # - GH_AW_GITHUB_TOKEN @@ -38,23 +39,25 @@ # Custom actions used: # - actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 # - actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 -# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 -# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 +# - actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 +# - actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 -# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 +# - actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 +# - github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.25.41 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41 -# - ghcr.io/github/gh-aw-firewall/squid:0.25.41 -# - ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c -# - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 -# - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f +# - ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d +# - ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d +# - ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be +# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b +# - ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 name: "Issue Repro Triage & Auto-Fix 🔍" -"on": +on: schedule: - cron: "28 */12 * * *" # Friendly format: every 12h (scattered) @@ -62,7 +65,7 @@ name: "Issue Repro Triage & Auto-Fix 🔍" inputs: aw_context: default: "" - description: Agent caller context (used internally by Agentic Workflows). + description: "Agent caller context (used internally by Agentic Workflows)." required: false type: string @@ -79,48 +82,55 @@ jobs: permissions: actions: read contents: read + env: + GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: comment_id: "" comment_repo: "" + daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }} + daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }} + daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }} engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} - secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} + safe-output-artifact-client: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} env: GH_AW_SETUP_WORKFLOW_NAME: "Issue Repro Triage & Auto-Fix 🔍" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-repro-triage.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.40" - GH_AW_INFO_AGENT_VERSION: "1.0.40" - GH_AW_INFO_CLI_VERSION: "v0.72.1" + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AGENT_VERSION: "1.0.65" + GH_AW_INFO_CLI_VERSION: "v0.81.6" GH_AW_INFO_WORKFLOW_NAME: "Issue Repro Triage & Auto-Fix 🔍" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","dotnet"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.41" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" - GITHUB_MCP_LOCKDOWN_EXPLICIT: "true" - GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} - GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | @@ -128,18 +138,58 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); - - name: Validate COPILOT_GITHUB_TOKEN secret - id: validate-secret - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-issuereprotriage-${{ github.run_id }} + restore-keys: agentic-workflow-usage-issuereprotriage- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Restore daily AIC usage cache (artifact fallback) + id: restore-daily-aic-cache-fallback + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_RESTORE_DAILY_AIC_CACHE_HIT: ${{ steps.restore-daily-aic-cache.outputs.cache-hit }} + GH_AW_RESTORE_DAILY_AIC_CACHE_MATCHED_KEY: ${{ steps.restore-daily-aic-cache.outputs.cache-matched-key }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs'); + await main(); + - name: Check daily workflow token guardrail + id: daily-effective-workflow-guardrail + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_WORKFLOW_NAME: "Issue Repro Triage & Auto-Fix 🔍" + GH_AW_WORKFLOW_ID: "issue-repro-triage" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} + GH_AW_HAS_SLASH_COMMAND: "false" + GH_AW_HAS_LABEL_COMMAND: "false" + GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs'); + await main(); - name: Checkout .github and .agents folders - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false sparse-checkout: | .github .agents + .antigravity .claude .codex .crush @@ -150,8 +200,8 @@ jobs: fetch-depth: 1 - name: Save agent config folders for base branch restoration env: - GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" - GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" # poutine:ignore untrusted_checkout_exec run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" - name: Check workflow lock file @@ -169,22 +219,25 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.72.1" + GH_AW_COMPILED_VERSION: "v0.81.6" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); await main(); + - name: Log runtime features + if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh" - name: Create prompt with built-in context env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} @@ -192,59 +245,59 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_751681ae529c44f8_EOF' + cat << 'GH_AW_PROMPT_19b1c9a05ee9255e_EOF' - GH_AW_PROMPT_751681ae529c44f8_EOF + GH_AW_PROMPT_19b1c9a05ee9255e_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_751681ae529c44f8_EOF' + cat << 'GH_AW_PROMPT_19b1c9a05ee9255e_EOF' Tools: add_comment(max:10), create_pull_request(max:3), add_labels(max:15), remove_labels(max:15), missing_tool, missing_data, noop - GH_AW_PROMPT_751681ae529c44f8_EOF + GH_AW_PROMPT_19b1c9a05ee9255e_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_751681ae529c44f8_EOF' + cat << 'GH_AW_PROMPT_19b1c9a05ee9255e_EOF' - GH_AW_PROMPT_751681ae529c44f8_EOF + GH_AW_PROMPT_19b1c9a05ee9255e_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_751681ae529c44f8_EOF' + cat << 'GH_AW_PROMPT_19b1c9a05ee9255e_EOF' The following GitHub context information is available for this workflow: - {{#if __GH_AW_GITHUB_ACTOR__ }} + {{#if github.actor}} - **actor**: __GH_AW_GITHUB_ACTOR__ {{/if}} - {{#if __GH_AW_GITHUB_REPOSITORY__ }} + {{#if github.repository}} - **repository**: __GH_AW_GITHUB_REPOSITORY__ {{/if}} - {{#if __GH_AW_GITHUB_WORKSPACE__ }} + {{#if github.workspace}} - **workspace**: __GH_AW_GITHUB_WORKSPACE__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} - - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}} + - **issue-number**: #__GH_AW_EXPR_802A9F6A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} - - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}} + - **discussion-number**: #__GH_AW_EXPR_1A3A194A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} - - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}} + - **pull-request-number**: #__GH_AW_EXPR_463A214A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} - - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{#if github.event.comment.id || github.aw.context.comment_id}} + - **comment-id**: __GH_AW_EXPR_FF1D34CE__ {{/if}} - {{#if __GH_AW_GITHUB_RUN_ID__ }} + {{#if github.run_id}} - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ {{/if}} - GH_AW_PROMPT_751681ae529c44f8_EOF + GH_AW_PROMPT_19b1c9a05ee9255e_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_751681ae529c44f8_EOF' + cat << 'GH_AW_PROMPT_19b1c9a05ee9255e_EOF' {{#runtime-import .github/workflows/shared/repo-build-setup.md}} {{#runtime-import .github/workflows/issue-repro-triage.md}} - GH_AW_PROMPT_751681ae529c44f8_EOF + GH_AW_PROMPT_19b1c9a05ee9255e_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -265,11 +318,11 @@ jobs: GH_AW_ALLOWED_EXTENSIONS: '' GH_AW_CACHE_DESCRIPTION: '' GH_AW_CACHE_DIR: '/tmp/gh-aw/cache-memory/' + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} @@ -288,11 +341,11 @@ jobs: GH_AW_ALLOWED_EXTENSIONS: process.env.GH_AW_ALLOWED_EXTENSIONS, GH_AW_CACHE_DESCRIPTION: process.env.GH_AW_CACHE_DESCRIPTION, GH_AW_CACHE_DIR: process.env.GH_AW_CACHE_DIR, + GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A, + GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A, + GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A, + GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE, GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, - GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, @@ -311,61 +364,78 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: activation include-hidden-files: true path: | /tmp/gh-aw/aw_info.json + /tmp/gh-aw/models.json /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/aw-prompts/prompt-template.txt /tmp/gh-aw/aw-prompts/prompt-import-tree.json /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/base /tmp/gh-aw/.github/agents + /tmp/gh-aw/.github/skills if-no-files-found: ignore retention-days: 1 agent: needs: activation + if: needs.activation.outputs.daily_ai_credits_exceeded != 'true' runs-on: ubuntu-latest permissions: contents: read + copilot-requests: write issues: read pull-requests: read concurrency: group: "gh-aw-copilot-${{ github.workflow }}" + queue: max env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} GH_AW_ASSETS_ALLOWED_EXTS: "" GH_AW_ASSETS_BRANCH: "" GH_AW_ASSETS_MAX_SIZE_KB: 0 GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: issuereprotriage outputs: - agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} + agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} + ai_credits_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.ai_credits_rate_limit_error || 'false' }} + aic: ${{ steps.parse-mcp-gateway.outputs.aic }} + ambient_context: ${{ steps.parse-mcp-gateway.outputs.ambient_context }} + cache_memory_restore_0_cache_hit: ${{ steps.restore_cache_memory_0.outputs.cache-hit || 'false' }} + cache_memory_restore_0_matched_key: ${{ steps.restore_cache_memory_0.outputs.cache-matched-key || '' }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} - inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} - mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} + inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} model: ${{ needs.activation.outputs.model }} - model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} + model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} output: ${{ steps.collect_output.outputs.output }} output_types: ${{ steps.collect_output.outputs.output_types }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} + unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Issue Repro Triage & Auto-Fix 🔍" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-repro-triage.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths run: | @@ -375,7 +445,7 @@ jobs: echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false - name: Create gh-aw temp directory @@ -388,6 +458,7 @@ jobs: - name: Create cache-memory directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_cache_memory_dir.sh" - name: Restore cache-memory file share data + id: restore_cache_memory_0 uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: key: memory-none-9f0b69b3-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }} @@ -401,21 +472,14 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/setup_cache_memory_git.sh" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Checkout PR branch id: checkout-pr if: | - github.event.pull_request || github.event.issue.pull_request + github.event.pull_request || github.event.issue.pull_request || github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -427,11 +491,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Parse integrity filter lists id: parse-guard-vars env: @@ -440,33 +504,35 @@ jobs: GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: activation path: /tmp/gh-aw - name: Restore agent config folders from base branch if: steps.checkout-pr.outcome == 'success' env: - GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" - GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" - name: Restore inline sub-agents from activation artifact env: GH_AW_SUB_AGENT_DIR: ".github/agents" GH_AW_SUB_AGENT_EXT: ".agent.md" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" + - name: Restore inline skills from activation artifact + env: + GH_AW_SKILL_DIR: ".github/skills" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41 ghcr.io/github/gh-aw-firewall/squid:0.25.41 ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 - name: Generate Safe Outputs Config - env: - GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << GH_AW_SAFE_OUTPUTS_CONFIG_c714e9e6a92b9818_EOF - {"add_comment":{"hide_older_comments":true,"max":10,"target":"*"},"add_labels":{"max":15},"create_pull_request":{"allowed_base_branches":["main","rel/*"],"draft":false,"github-token":"${GH_AW_GITHUB_TOKEN}","max":3,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"fallback-to-issue","title_prefix":"[fix] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"remove_labels":{"max":15},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_c714e9e6a92b9818_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_7ecf424fe3e8510f_EOF' + {"add_comment":{"hide_older_comments":true,"max":10,"target":"*"},"add_labels":{"max":15},"create_pull_request":{"allowed_base_branches":["main","rel/*"],"draft":false,"max":3,"max_patch_files":100,"max_patch_size":4096,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"fallback-to-issue","title_prefix":"[fix] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"remove_labels":{"max":15},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_7ecf424fe3e8510f_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -512,10 +578,7 @@ jobs: }, "labels": { "required": true, - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 + "type": "array" }, "repo": { "type": "string", @@ -629,10 +692,7 @@ jobs: }, "labels": { "required": true, - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 + "type": "array" }, "repo": { "type": "string", @@ -664,53 +724,15 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); await main(); - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - - name: Start MCP Gateway id: start-mcp-gateway env: + GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }} GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -eo pipefail mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" @@ -730,21 +752,25 @@ jobs: export GH_AW_ENGINE="copilot" MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') - DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.6' + case "${DOCKER_HOST:-}" in + unix://* ) DOCKER_SOCK_PATH="${DOCKER_HOST#unix://}" ;; + /* ) DOCKER_SOCK_PATH="$DOCKER_HOST" ;; + * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; + esac + DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --name awmg-mcpg --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.3.30' - mkdir -p /home/runner/.copilot + mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_e92b145954301bf1_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_1c674ef1299e6295_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.0.3", + "container": "ghcr.io/github/github-mcp-server:v1.4.0", "env": { - "GITHUB_HOST": "\${GITHUB_SERVER_URL}", - "GITHUB_LOCKDOWN_MODE": "1", - "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_HOST": "${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", "GITHUB_TOOLSETS": "issues,repos,pull_requests" }, @@ -759,10 +785,27 @@ jobs: } }, "safeoutputs": { - "type": "http", - "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + "type": "stdio", + "container": "ghcr.io/github/gh-aw-node", + "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"], + "args": ["-w", "\${GITHUB_WORKSPACE}"], + "entrypoint": "sh", + "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], + "env": { + "DEBUG": "*", + "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", + "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", + "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", + "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", + "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", + "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", + "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", + "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", + "GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}", + "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", + "GITHUB_TOKEN": "\${GITHUB_TOKEN}", + "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", + "RUNNER_TEMP": "\${RUNNER_TEMP}" }, "guard-policies": { "write-sink": { @@ -780,7 +823,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_e92b145954301bf1_EOF + GH_AW_MCP_CONFIG_1c674ef1299e6295_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -808,25 +851,54 @@ jobs: timeout-minutes: 45 run: | set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + mkdir -p "$HOME/.copilot" + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" + export XDG_CONFIG_HOME="$HOME" + export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json" touch /tmp/gh-aw/agent-step-summary.md GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.41/awf-config.schema.json","network":{"allowDomains":["*.vsblob.vsassets.io","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.nuget.org","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","azuresearch-usnc.nuget.org","azuresearch-ussc.nuget.org","builds.dotnet.microsoft.com","ci.dot.net","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","dc.services.visualstudio.com","dist.nuget.org","dot.net","dotnet.microsoft.com","dotnetcli.blob.core.windows.net","github.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","nuget.org","nuget.pkg.github.com","nugetregistryv2prod.blob.core.windows.net","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","oneocsp.microsoft.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","pkgs.dev.azure.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com","www.microsoft.com"]},"apiProxy":{"enabled":true,"models":{"auto":["large"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"]}},"container":{"imageTag":"0.25.41"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir /tmp/gh-aw/cache-memory/ --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.vsblob.vsassets.io\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.nuget.org\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"azuresearch-usnc.nuget.org\",\"azuresearch-ussc.nuget.org\",\"builds.dotnet.microsoft.com\",\"ci.dot.net\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"dc.services.visualstudio.com\",\"dist.nuget.org\",\"dot.net\",\"dotnet.microsoft.com\",\"dotnetcli.blob.core.windows.net\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"nuget.org\",\"nuget.pkg.github.com\",\"nugetregistryv2prod.blob.core.windows.net\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"oneocsp.microsoft.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"pkgs.dev.azure.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\",\"www.microsoft.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + GH_AW_CHROOT_BINARIES_SOURCE_PATH=/tmp/gh-aw GH_AW_CHROOT_IDENTITY_HOME=/tmp/gh-aw/home node "${RUNNER_TEMP}/gh-aw/actions/patch_awf_chroot_config.cjs" + fi + GH_AW_TOOL_CACHE_MOUNT="" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" + if [ -d "$GH_AW_TOOL_CACHE" ]; then + if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then + GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" + fi + fi + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir /tmp/gh-aw/cache-memory/ --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_API_KEY: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ github.token }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} + GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.72.1 + GH_AW_TIMEOUT_MINUTES: 45 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -840,25 +912,20 @@ jobs: GIT_AUTHOR_NAME: github-actions[bot] GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] - XDG_CONFIG_HOME: /home/runner - - name: Detect Copilot errors - id: detect-copilot-errors + RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} + - name: Detect agent errors if: always() + id: detect-agent-errors continue-on-error: true - run: node "${RUNNER_TEMP}/gh-aw/actions/detect_copilot_errors.cjs" + run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Copy Copilot session state files to logs if: always() continue-on-error: true @@ -882,8 +949,7 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); await main(); env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -979,16 +1045,23 @@ jobs: env: GH_AW_CACHE_DIR: /tmp/gh-aw/cache-memory run: bash "${RUNNER_TEMP}/gh-aw/actions/commit_cache_memory_git.sh" + - name: Check cache-memory git integrity + if: always() + continue-on-error: true + env: + GH_AW_CACHE_DIR: /tmp/gh-aw/cache-memory + run: bash "${RUNNER_TEMP}/gh-aw/actions/check_cache_memory_git_integrity.sh" - name: Upload cache-memory data as artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 if: always() with: name: cache-memory + include-hidden-files: true path: /tmp/gh-aw/cache-memory - name: Upload agent artifacts if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: agent path: | @@ -1022,16 +1095,18 @@ jobs: - update_cache_memory if: > always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || - needs.activation.outputs.stale_lock_file_failed == 'true') + needs.activation.outputs.stale_lock_file_failed == 'true' || needs.activation.outputs.daily_ai_credits_exceeded == 'true') runs-on: ubuntu-slim permissions: contents: write - discussions: write issues: write pull-requests: write concurrency: group: "gh-aw-conclusion-issue-repro-triage" cancel-in-progress: false + queue: max + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} @@ -1040,19 +1115,35 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Issue Repro Triage & Auto-Fix 🔍" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-repro-triage.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Generate GitHub App token + id: safe-outputs-app-token + if: ${{ vars.APP_ID != '' && secrets.APP_PRIVATE_KEY != '' }} + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + repositories: ${{ github.event.repository.name }} + github-api-url: ${{ github.api_url }} + permission-contents: write + permission-issues: write + permission-pull-requests: write - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1063,6 +1154,88 @@ jobs: mkdir -p /tmp/gh-aw/ find "/tmp/gh-aw/" -type f -print echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Collect usage artifact files + if: always() + continue-on-error: true + run: | + mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection + echo "Usage artifact source file status:" + for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do + [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file" + done + [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true + [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true + [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true + [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true + [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true + [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl + [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl + mkdir -p /tmp/gh-aw/usage/activity + node ${{ runner.temp }}/gh-aw/actions/generate_usage_activity_summary.cjs + find /tmp/gh-aw/usage -type f -print | sort + - name: Upload usage artifact + if: always() + continue-on-error: true + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: usage + path: | + /tmp/gh-aw/usage/aw_info.json + /tmp/gh-aw/usage/aw-info.jsonl + /tmp/gh-aw/usage/agent_usage.json + /tmp/gh-aw/usage/agent_usage.jsonl + /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/github_rate_limits.jsonl + /tmp/gh-aw/usage/agent/token_usage.jsonl + /tmp/gh-aw/usage/detection/token_usage.jsonl + /tmp/gh-aw/usage/activity/summary.json + if-no-files-found: ignore + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache-conclusion + if: always() + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-issuereprotriage-${{ github.run_id }} + restore-keys: agentic-workflow-usage-issuereprotriage- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Write daily AIC usage cache entry + id: write-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context); + const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs'); + await main(); + - name: Save daily AIC usage cache + id: save-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-issuereprotriage-${{ github.run_id }} + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Upload daily AIC usage cache artifact + id: upload-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: aic-usage-cache + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + if-no-files-found: ignore + retention-days: 7 - name: Process no-op messages id: noop uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1070,11 +1243,16 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" GH_AW_WORKFLOW_NAME: "Issue Repro Triage & Auto-Fix 🔍" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-repro-triage.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_NOOP_REPORT_AS_ISSUE: "false" + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} + GH_AW_WORKFLOW_ID: "issue-repro-triage" with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ steps.safe-outputs-app-token.outputs.token || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -1086,11 +1264,12 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "Issue Repro Triage & Auto-Fix 🔍" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-repro-triage.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ steps.safe-outputs-app-token.outputs.token || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -1103,8 +1282,9 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" GH_AW_WORKFLOW_NAME: "Issue Repro Triage & Auto-Fix 🔍" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-repro-triage.md" with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ steps.safe-outputs-app-token.outputs.token || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -1117,8 +1297,9 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" GH_AW_WORKFLOW_NAME: "Issue Repro Triage & Auto-Fix 🔍" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-repro-triage.md" with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ steps.safe-outputs-app-token.outputs.token || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -1131,13 +1312,19 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "Issue Repro Triage & Auto-Fix 🔍" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-repro-triage.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_WORKFLOW_ID: "issue-repro-triage" GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" - GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} + GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} + GH_AW_UNKNOWN_MODEL_AI_CREDITS: ${{ needs.agent.outputs.unknown_model_ai_credits || 'false' }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} @@ -1145,8 +1332,13 @@ jobs: GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }} GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }} + GH_AW_SAFE_OUTPUTS_APP_TOKEN_MINTING_FAILED: ${{ needs.safe_outputs.outputs.app_token_minting_failed }} + GH_AW_CONCLUSION_APP_TOKEN_MINTING_FAILED: ${{ steps.safe-outputs-app-token.outcome == 'failure' }} GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_DAILY_AI_CREDITS_EXCEEDED: ${{ needs.activation.outputs.daily_ai_credits_exceeded }} + GH_AW_DAILY_AI_CREDITS_TOTAL_EFFECTIVE_TOKENS: ${{ needs.activation.outputs.daily_ai_credits_total_effective_tokens }} + GH_AW_DAILY_AI_CREDITS_THRESHOLD: ${{ needs.activation.outputs.daily_ai_credits_threshold }} GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔍 *Triaged by [{workflow_name}]({run_url})*\"}" GH_AW_GROUP_REPORTS: "false" GH_AW_FAILURE_REPORT_AS_ISSUE: "true" @@ -1154,8 +1346,10 @@ jobs: GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" GH_AW_TIMEOUT_MINUTES: "45" GH_AW_CACHE_MEMORY_ENABLED: "true" + GH_AW_CACHE_MEMORY_RESTORE_0_MATCHED_KEY: ${{ needs.agent.outputs.cache_memory_restore_0_matched_key || '' }} + GH_AW_CACHE_MEMORY_RESTORE_0_CACHE_HIT: ${{ needs.agent.outputs.cache_memory_restore_0_cache_hit || 'false' }} with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ steps.safe-outputs-app-token.outputs.token || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -1166,31 +1360,37 @@ jobs: needs: - activation - agent - if: > - always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + if: always() && needs.agent.result != 'skipped' runs-on: ubuntu-latest permissions: contents: read + copilot-requests: write + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: + aic: ${{ steps.parse_detection_token_usage.outputs.aic }} detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} detection_reason: ${{ steps.detection_conclusion.outputs.reason }} detection_success: ${{ steps.detection_conclusion.outputs.success }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Issue Repro Triage & Auto-Fix 🔍" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-repro-triage.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1203,7 +1403,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Checkout repository for patch context if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false # --- Threat Detection --- @@ -1212,7 +1412,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41 ghcr.io/github/gh-aw-firewall/squid:0.25.41 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d - name: Check if detection needed id: detection_guard if: always() @@ -1231,13 +1431,17 @@ jobs: if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" - rm -f /home/runner/.copilot/mcp-config.json + rm -f "$HOME/.copilot/mcp-config.json" rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" - name: Prepare threat detection files if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + rm -f /tmp/gh-aw/agent_usage.json cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then + echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context." + fi cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true for f in /tmp/gh-aw/aw-*.patch; do [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true @@ -1271,11 +1475,11 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1284,23 +1488,54 @@ jobs: timeout-minutes: 20 run: | set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + mkdir -p "$HOME/.copilot" + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" + export XDG_CONFIG_HOME="$HOME" touch /tmp/gh-aw/agent-step-summary.md GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.41/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true},"container":{"imageTag":"0.25.41"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + _GH_AW_CHROOT_JSON=$(jq -c --arg src /tmp/gh-aw --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home /tmp/gh-aw/home '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; } + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "/tmp/gh-aw/awf-config.json" + fi + GH_AW_TOOL_CACHE_MOUNT="" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" + if [ -d "$GH_AW_TOOL_CACHE" ]; then + if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then + GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" + fi + fi + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_API_KEY: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }} + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ github.token }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }} + GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.72.1 + GH_AW_TIMEOUT_MINUTES: 20 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1313,10 +1548,25 @@ jobs: GIT_AUTHOR_NAME: github-actions[bot] GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] - XDG_CONFIG_HOME: /home/runner + RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} + - name: Parse threat detection token usage for step summary + id: parse_detection_token_usage + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); - name: Upload threat detection log if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: detection path: /tmp/gh-aw/threat-detection/detection.log @@ -1328,6 +1578,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }} GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" with: script: | @@ -1338,10 +1589,11 @@ jobs: await main(); } catch (loadErr) { const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure'; const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); core.error(msg); core.setOutput('reason', 'parse_error'); - if (continueOnError) { + if (continueOnError && !detectionExecutionFailed) { core.warning('\u26A0\uFE0F ' + msg); core.setOutput('conclusion', 'warning'); core.setOutput('success', 'false'); @@ -1361,22 +1613,28 @@ jobs: runs-on: ubuntu-slim permissions: contents: write - discussions: write issues: write pull-requests: write - timeout-minutes: 15 + timeout-minutes: 45 env: + GH_AW_AGENT_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/issue-repro-triage" GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.40" + GH_AW_ENGINE_VERSION: "1.0.65" + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔍 *Triaged by [{workflow_name}]({run_url})*\"}" + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "issue-repro-triage" GH_AW_WORKFLOW_NAME: "Issue Repro Triage & Auto-Fix 🔍" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-repro-triage.md" outputs: + app_token_minting_failed: ${{ steps.safe-outputs-app-token.outcome == 'failure' }} code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} comment_id: ${{ steps.process_safe_outputs.outputs.comment_id }} @@ -1390,19 +1648,22 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Issue Repro Triage & Auto-Fix 🔍" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-repro-triage.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1415,59 +1676,40 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Download patch artifact continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ - - name: Extract base branch from agent output - id: extract-base-branch - if: steps.download-agent-output.outcome == 'success' - shell: bash - run: | - if [ -f "/tmp/gh-aw/agent_output.json" ]; then - GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - BASE_BRANCH=$("$GH_AW_NODE" -e " - try { - const data = JSON.parse(require('fs').readFileSync('/tmp/gh-aw/agent_output.json', 'utf8')); - const item = (data.items || []).find(i => - (i.type === 'create_pull_request' || i.type === 'push_to_pull_request_branch') && - i.base_branch - ); - if (item) process.stdout.write(item.base_branch); - } catch(e) {} - " 2>/dev/null || true) - # Validate: only allow safe git branch name characters - if [[ "$BASE_BRANCH" =~ ^[a-zA-Z0-9/_.-]+$ ]] && [ ${#BASE_BRANCH} -le 255 ]; then - printf 'base-branch=%s\n' "$BASE_BRANCH" >> "$GITHUB_OUTPUT" - echo "Extracted base branch from safe output: $BASE_BRANCH" - fi - fi + - name: Generate GitHub App token + id: safe-outputs-app-token + if: ${{ vars.APP_ID != '' && secrets.APP_PRIVATE_KEY != '' }} + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + repositories: ${{ github.event.repository.name }} + github-api-url: ${{ github.api_url }} + permission-contents: write + permission-issues: write + permission-pull-requests: write - name: Checkout repository if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: - ref: ${{ steps.extract-base-branch.outputs.base-branch || github.base_ref || github.event.pull_request.base.ref || github.ref_name || github.event.repository.default_branch }} - token: ${{ secrets.GH_AW_GITHUB_TOKEN }} - persist-credentials: false - fetch-depth: 1 + persist-credentials: true + token: ${{ steps.safe-outputs-app-token.outputs.token || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} - GIT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GIT_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GIT_TOKEN: ${{ steps.safe-outputs-app-token.outputs.token || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash - run: | + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. GH_HOST="${GITHUB_SERVER_URL#https://}" @@ -1478,14 +1720,15 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} GH_AW_ALLOWED_DOMAINS: "*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":10,\"target\":\"*\"},\"add_labels\":{\"max\":15},\"create_pull_request\":{\"allowed_base_branches\":[\"main\",\"rel/*\"],\"draft\":false,\"github-token\":\"${{ secrets.GH_AW_GITHUB_TOKEN }}\",\"max\":3,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"fallback-to-issue\",\"title_prefix\":\"[fix] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"remove_labels\":{\"max\":15},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":10,\"target\":\"*\"},\"add_labels\":{\"max\":15},\"create_pull_request\":{\"allowed_base_branches\":[\"main\",\"rel/*\"],\"draft\":false,\"max\":3,\"max_patch_files\":100,\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"fallback-to-issue\",\"title_prefix\":\"[fix] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"remove_labels\":{\"max\":15},\"report_incomplete\":{}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ steps.safe-outputs-app-token.outputs.token || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ steps.safe-outputs-app-token.outputs.token || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -1493,7 +1736,7 @@ jobs: await main(); - name: Upload Safe Outputs Items if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: safe-outputs-items path: | @@ -1506,28 +1749,30 @@ jobs: - activation - agent - detection - if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: {} env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: issuereprotriage steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Issue Repro Triage & Auto-Fix 🔍" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-repro-triage.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download cache-memory artifact (default) id: download_cache_default - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 continue-on-error: true with: name: cache-memory diff --git a/.github/workflows/issue-repro-triage.md b/.github/workflows/issue-repro-triage.md index 016a3e5fd1..85895db5ce 100644 --- a/.github/workflows/issue-repro-triage.md +++ b/.github/workflows/issue-repro-triage.md @@ -12,6 +12,7 @@ permissions: contents: read issues: read pull-requests: read + copilot-requests: write network: allowed: @@ -21,13 +22,20 @@ network: tools: cache-memory: true github: - lockdown: true toolsets: [issues, repos, pull_requests] min-integrity: none bash: true edit: safe-outputs: + # Prefer an org-owned GitHub App: it mints a short-lived, auto-revoked token + # scoped to this job and (unlike GITHUB_TOKEN) triggers CI on the PR it opens. + # ignore-if-missing lets the workflow fall back to GITHUB_TOKEN when the App + # secrets are absent, so it still runs without org-admin setup and on forks. + github-app: + client-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + ignore-if-missing: true add-comment: max: 10 target: "*" @@ -42,7 +50,6 @@ safe-outputs: max: 3 allowed-base-branches: ["main", "rel/*"] protected-files: fallback-to-issue - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN }} noop: report-as-issue: false messages: diff --git a/.github/workflows/malicious-code-scan.lock.yml b/.github/workflows/malicious-code-scan.lock.yml index 4044465a32..f31e51f9f6 100644 --- a/.github/workflows/malicious-code-scan.lock.yml +++ b/.github/workflows/malicious-code-scan.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"d502c6f5b9e4161edc94b5916b896f5c39239f5e45c8ab6c9d1876410b28f3ff","body_hash":"fcbd527fee5e91a4204a556ea04f5174aafb79a1750d755c9ad92cd91c02aed0","compiler_version":"v0.79.8","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.60"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/codeql-action/upload-sarif","sha":"8aad20d150bbac5944a9f9d289da16a4b0d87c1e","version":"v4.36.2"},{"repo":"github/gh-aw-actions/setup","sha":"c0338fef4749d08c21f8f975fb0e37efa17dda47","version":"v0.79.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]} -# This file was automatically generated by gh-aw (v0.79.8). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"80a181c54e32a74f60cfe4bb620c98671dab76f77e47ed2e2851a4c303d7d518","body_hash":"fcbd527fee5e91a4204a556ea04f5174aafb79a1750d755c9ad92cd91c02aed0","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} +# gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/codeql-action/upload-sarif","sha":"8aad20d150bbac5944a9f9d289da16a4b0d87c1e","version":"v4.36.2"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} +# This file was automatically generated by gh-aw (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -26,26 +26,29 @@ # Automated security scan that reviews code changes from the last 3 days for suspicious patterns indicating malicious or agentic threats # # Secrets used: -# - COPILOT_GITHUB_TOKEN # - GH_AW_GITHUB_MCP_SERVER_TOKEN # - GH_AW_GITHUB_TOKEN # - GITHUB_TOKEN # # Custom actions used: -# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 -# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 +# - actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 +# - actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 +# - actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) -# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 +# - actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 # - github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 -# - github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 +# - github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 -# - ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa -# - ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c +# - ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d +# - ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d +# - ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be +# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b +# - ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 name: "Malicious Code Scan Agent" on: @@ -75,6 +78,7 @@ jobs: contents: read env: GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: comment_id: "" comment_repo: "" @@ -84,7 +88,6 @@ jobs: engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} - secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} @@ -92,7 +95,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -100,8 +103,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Malicious Code Scan Agent" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/malicious-code-scan.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info @@ -109,16 +112,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AGENT_VERSION: "1.0.60" - GH_AW_INFO_CLI_VERSION: "v0.79.8" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AGENT_VERSION: "1.0.65" + GH_AW_INFO_CLI_VERSION: "v0.81.6" GH_AW_INFO_WORKFLOW_NAME: "Malicious Code Scan Agent" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -129,6 +132,30 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-maliciouscodescan-${{ github.run_id }} + restore-keys: agentic-workflow-usage-maliciouscodescan- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Restore daily AIC usage cache (artifact fallback) + id: restore-daily-aic-cache-fallback + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_RESTORE_DAILY_AIC_CACHE_HIT: ${{ steps.restore-daily-aic-cache.outputs.cache-hit }} + GH_AW_RESTORE_DAILY_AIC_CACHE_MATCHED_KEY: ${{ steps.restore-daily-aic-cache.outputs.cache-matched-key }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs'); + await main(); - name: Check daily workflow token guardrail id: daily-effective-workflow-guardrail if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} @@ -138,6 +165,8 @@ jobs: GH_AW_WORKFLOW_ID: "malicious-code-scan" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} + GH_AW_HAS_SLASH_COMMAND: "false" + GH_AW_HAS_LABEL_COMMAND: "false" GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} with: @@ -147,13 +176,8 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs'); await main(); - - name: Validate COPILOT_GITHUB_TOKEN secret - id: validate-secret - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Checkout .github and .agents folders - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false sparse-checkout: | @@ -189,13 +213,16 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.79.8" + GH_AW_COMPILED_VERSION: "v0.81.6" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); await main(); + - name: Log runtime features + if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh" - name: Create prompt with built-in context env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt @@ -212,20 +239,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_09e22fe76094f6c0_EOF' + cat << 'GH_AW_PROMPT_47a4e26d6e8a1dcc_EOF' - GH_AW_PROMPT_09e22fe76094f6c0_EOF + GH_AW_PROMPT_47a4e26d6e8a1dcc_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_09e22fe76094f6c0_EOF' + cat << 'GH_AW_PROMPT_47a4e26d6e8a1dcc_EOF' Tools: create_code_scanning_alert, missing_tool, missing_data, noop - GH_AW_PROMPT_09e22fe76094f6c0_EOF + GH_AW_PROMPT_47a4e26d6e8a1dcc_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_09e22fe76094f6c0_EOF' + cat << 'GH_AW_PROMPT_47a4e26d6e8a1dcc_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -254,12 +281,12 @@ jobs: {{/if}} - GH_AW_PROMPT_09e22fe76094f6c0_EOF + GH_AW_PROMPT_47a4e26d6e8a1dcc_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_09e22fe76094f6c0_EOF' + cat << 'GH_AW_PROMPT_47a4e26d6e8a1dcc_EOF' {{#runtime-import .github/workflows/malicious-code-scan.md}} - GH_AW_PROMPT_09e22fe76094f6c0_EOF + GH_AW_PROMPT_47a4e26d6e8a1dcc_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -320,7 +347,7 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: activation include-hidden-files: true @@ -344,6 +371,7 @@ jobs: permissions: actions: read contents: read + copilot-requests: write security-events: read concurrency: group: "gh-aw-copilot-${{ github.workflow }}" @@ -354,6 +382,7 @@ jobs: GH_AW_ASSETS_BRANCH: "" GH_AW_ASSETS_MAX_SIZE_KB: 0 GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: maliciouscodescan outputs: agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} @@ -376,7 +405,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -385,8 +414,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Malicious Code Scan Agent" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/malicious-code-scan.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -397,7 +426,7 @@ jobs: echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false - name: Create gh-aw temp directory @@ -408,17 +437,10 @@ jobs: GH_TOKEN: ${{ github.token }} - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Checkout PR branch id: checkout-pr if: | @@ -434,11 +456,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) @@ -450,7 +472,7 @@ jobs: const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: activation path: /tmp/gh-aw @@ -470,15 +492,15 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_0ef2dabc4e1436a6_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_ba0dd4cecb560db0_EOF' {"create_code_scanning_alert":{"driver":"Malicious Code Scanner"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_0ef2dabc4e1436a6_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_ba0dd4cecb560db0_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -611,55 +633,17 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); await main(); - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - - name: Start MCP Gateway id: start-mcp-gateway env: + GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }} GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }} GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }} GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -eo pipefail mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" @@ -685,19 +669,19 @@ jobs: * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; esac DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.25' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --name awmg-mcpg --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.3.30' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_2c4693c858dfc320_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_a2c953552c7d02c8_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.1.2", + "container": "ghcr.io/github/github-mcp-server:v1.4.0", "env": { - "GITHUB_HOST": "\${GITHUB_SERVER_URL}", - "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_HOST": "${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", "GITHUB_TOOLSETS": "repos,code_security" }, @@ -709,10 +693,27 @@ jobs: } }, "safeoutputs": { - "type": "http", - "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + "type": "stdio", + "container": "ghcr.io/github/gh-aw-node", + "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"], + "args": ["-w", "\${GITHUB_WORKSPACE}"], + "entrypoint": "sh", + "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], + "env": { + "DEBUG": "*", + "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", + "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", + "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", + "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", + "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", + "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", + "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", + "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", + "GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}", + "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", + "GITHUB_TOKEN": "\${GITHUB_TOKEN}", + "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", + "RUNNER_TEMP": "\${RUNNER_TEMP}" }, "guard-policies": { "write-sink": { @@ -730,7 +731,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_2c4693c858dfc320_EOF + GH_AW_MCP_CONFIG_a2c953552c7d02c8_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -769,38 +770,43 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + GH_AW_CHROOT_BINARIES_SOURCE_PATH=/tmp/gh-aw GH_AW_CHROOT_IDENTITY_HOME=/tmp/gh-aw/home node "${RUNNER_TEMP}/gh-aw/actions/patch_awf_chroot_config.cjs" fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -815,6 +821,8 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Detect agent errors if: always() id: detect-agent-errors @@ -822,17 +830,10 @@ jobs: run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Copy Copilot session state files to logs if: always() continue-on-error: true @@ -856,8 +857,7 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); await main(); env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -951,7 +951,7 @@ jobs: - name: Upload agent artifacts if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: agent path: | @@ -989,6 +989,8 @@ jobs: group: "gh-aw-conclusion-malicious-code-scan" cancel-in-progress: false queue: max + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} @@ -997,7 +999,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1006,13 +1008,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Malicious Code Scan Agent" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/malicious-code-scan.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1029,34 +1031,82 @@ jobs: run: | mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection echo "Usage artifact source file status:" - for file in /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do + for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file" done + [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true + [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl + mkdir -p /tmp/gh-aw/usage/activity + node ${{ runner.temp }}/gh-aw/actions/generate_usage_activity_summary.cjs find /tmp/gh-aw/usage -type f -print | sort - name: Upload usage artifact if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: usage path: | + /tmp/gh-aw/usage/aw_info.json /tmp/gh-aw/usage/aw-info.jsonl + /tmp/gh-aw/usage/agent_usage.json /tmp/gh-aw/usage/agent_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/github_rate_limits.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl + /tmp/gh-aw/usage/activity/summary.json if-no-files-found: ignore + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache-conclusion + if: always() + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-maliciouscodescan-${{ github.run_id }} + restore-keys: agentic-workflow-usage-maliciouscodescan- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Write daily AIC usage cache entry + id: write-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context); + const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs'); + await main(); + - name: Save daily AIC usage cache + id: save-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-maliciouscodescan-${{ github.run_id }} + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Upload daily AIC usage cache artifact + id: upload-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: aic-usage-cache + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + if-no-files-found: ignore + retention-days: 7 - name: Process no-op messages id: noop uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1125,7 +1175,6 @@ jobs: GH_AW_WORKFLOW_ID: "malicious-code-scan" GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" - GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} @@ -1173,7 +1222,8 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.60" + GH_AW_ENGINE_VERSION: "1.0.65" + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_TRACKER_ID: "malicious-code-scan" GH_AW_WORKFLOW_ID: "malicious-code-scan" GH_AW_WORKFLOW_NAME: "Malicious Code Scan Agent" @@ -1189,7 +1239,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1198,13 +1248,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Malicious Code Scan Agent" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/malicious-code-scan.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1218,8 +1268,7 @@ jobs: - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash - # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. - run: | + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. GH_HOST="${GITHUB_SERVER_URL#https://}" @@ -1244,7 +1293,7 @@ jobs: await main(); - name: Upload SARIF artifact if: steps.process_safe_outputs.outputs.sarif_file != '' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: code-scanning-sarif path: ${{ steps.process_safe_outputs.outputs.sarif_file }} @@ -1252,7 +1301,7 @@ jobs: retention-days: 1 - name: Upload Safe Outputs Items if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: safe-outputs-items path: | @@ -1269,9 +1318,11 @@ jobs: contents: read security-events: write timeout-minutes: 10 + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} steps: - name: Restore checkout to triggering commit - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: ${{ github.sha }} token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -1279,7 +1330,7 @@ jobs: fetch-depth: 1 - name: Download SARIF artifact continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: code-scanning-sarif path: /tmp/gh-aw/sarif/ diff --git a/.github/workflows/malicious-code-scan.md b/.github/workflows/malicious-code-scan.md index 8327ca08f3..340bae5d33 100644 --- a/.github/workflows/malicious-code-scan.md +++ b/.github/workflows/malicious-code-scan.md @@ -9,6 +9,7 @@ permissions: contents: read actions: read security-events: read + copilot-requests: write tracker-id: malicious-code-scan diff --git a/.github/workflows/markdown-linter.lock.yml b/.github/workflows/markdown-linter.lock.yml index 24087c351f..47f61daed3 100644 --- a/.github/workflows/markdown-linter.lock.yml +++ b/.github/workflows/markdown-linter.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"da3681f86c2bfbfc2fd78d4cd1bfd097ea6ceb3c9a7e306916a660debecbd117","body_hash":"ed3f1406f293f05f3f2ab01d7bff4688be36f7d1e7968ee6d049bee6c903d91d","compiler_version":"v0.79.8","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.60"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/gh-aw-actions/setup","sha":"c0338fef4749d08c21f8f975fb0e37efa17dda47","version":"v0.79.8"},{"repo":"super-linter/super-linter","sha":"9e863354e3ff62e0727d37183162c4a88873df41","version":"v8.6.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]} -# This file was automatically generated by gh-aw (v0.79.8). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"0c8eb89590e3b30c39379b64f5f885ae1e3be3dd796c41b23d077abcc7f48670","body_hash":"ed3f1406f293f05f3f2ab01d7bff4688be36f7d1e7968ee6d049bee6c903d91d","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} +# gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"},{"repo":"super-linter/super-linter","sha":"9e863354e3ff62e0727d37183162c4a88873df41","version":"v8.6.0"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} +# This file was automatically generated by gh-aw (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -30,7 +30,6 @@ # - shared/reporting.md # # Secrets used: -# - COPILOT_GITHUB_TOKEN # - GH_AW_GITHUB_MCP_SERVER_TOKEN # - GH_AW_GITHUB_TOKEN # - GITHUB_TOKEN @@ -39,23 +38,22 @@ # - actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 # - actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 # - actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 -# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 -# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 # - actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 -# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 # - actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 -# - github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 +# - github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 # - super-linter/super-linter@9e863354e3ff62e0727d37183162c4a88873df41 # v8.6.0 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 -# - ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa -# - ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c +# - ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d +# - ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d +# - ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be +# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b +# - ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 name: "Markdown Linter" on: @@ -84,6 +82,7 @@ jobs: contents: read env: GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: comment_id: "" comment_repo: "" @@ -93,7 +92,6 @@ jobs: engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} - secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} @@ -101,7 +99,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -109,8 +107,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Markdown Linter" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/markdown-linter.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info @@ -118,16 +116,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AGENT_VERSION: "1.0.60" - GH_AW_INFO_CLI_VERSION: "v0.79.8" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AGENT_VERSION: "1.0.65" + GH_AW_INFO_CLI_VERSION: "v0.81.6" GH_AW_INFO_WORKFLOW_NAME: "Markdown Linter" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -138,6 +136,30 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-markdownlinter-${{ github.run_id }} + restore-keys: agentic-workflow-usage-markdownlinter- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Restore daily AIC usage cache (artifact fallback) + id: restore-daily-aic-cache-fallback + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_RESTORE_DAILY_AIC_CACHE_HIT: ${{ steps.restore-daily-aic-cache.outputs.cache-hit }} + GH_AW_RESTORE_DAILY_AIC_CACHE_MATCHED_KEY: ${{ steps.restore-daily-aic-cache.outputs.cache-matched-key }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs'); + await main(); - name: Check daily workflow token guardrail id: daily-effective-workflow-guardrail if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} @@ -147,6 +169,8 @@ jobs: GH_AW_WORKFLOW_ID: "markdown-linter" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} + GH_AW_HAS_SLASH_COMMAND: "false" + GH_AW_HAS_LABEL_COMMAND: "false" GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} with: @@ -156,13 +180,8 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs'); await main(); - - name: Validate COPILOT_GITHUB_TOKEN secret - id: validate-secret - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Checkout .github and .agents folders - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false sparse-checkout: | @@ -198,13 +217,16 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.79.8" + GH_AW_COMPILED_VERSION: "v0.81.6" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); await main(); + - name: Log runtime features + if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh" - name: Create prompt with built-in context env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt @@ -222,21 +244,21 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_9e1a88a4324629d9_EOF' + cat << 'GH_AW_PROMPT_b9537e11ab1694d0_EOF' - GH_AW_PROMPT_9e1a88a4324629d9_EOF + GH_AW_PROMPT_b9537e11ab1694d0_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_9e1a88a4324629d9_EOF' + cat << 'GH_AW_PROMPT_b9537e11ab1694d0_EOF' Tools: create_issue, missing_tool, missing_data, noop - GH_AW_PROMPT_9e1a88a4324629d9_EOF + GH_AW_PROMPT_b9537e11ab1694d0_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_9e1a88a4324629d9_EOF' + cat << 'GH_AW_PROMPT_b9537e11ab1694d0_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -265,13 +287,13 @@ jobs: {{/if}} - GH_AW_PROMPT_9e1a88a4324629d9_EOF + GH_AW_PROMPT_b9537e11ab1694d0_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_9e1a88a4324629d9_EOF' + cat << 'GH_AW_PROMPT_b9537e11ab1694d0_EOF' {{#runtime-import .github/workflows/shared/reporting.md}} {{#runtime-import .github/workflows/markdown-linter.md}} - GH_AW_PROMPT_9e1a88a4324629d9_EOF + GH_AW_PROMPT_b9537e11ab1694d0_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -343,7 +365,7 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: activation include-hidden-files: true @@ -369,6 +391,7 @@ jobs: permissions: actions: read contents: read + copilot-requests: write issues: read pull-requests: read concurrency: @@ -380,12 +403,15 @@ jobs: GH_AW_ASSETS_BRANCH: "" GH_AW_ASSETS_MAX_SIZE_KB: 0 GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: markdownlinter outputs: agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} ai_credits_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.ai_credits_rate_limit_error || 'false' }} aic: ${{ steps.parse-mcp-gateway.outputs.aic }} ambient_context: ${{ steps.parse-mcp-gateway.outputs.ambient_context }} + cache_memory_restore_0_cache_hit: ${{ steps.restore_cache_memory_0.outputs.cache-hit || 'false' }} + cache_memory_restore_0_matched_key: ${{ steps.restore_cache_memory_0.outputs.cache-matched-key || '' }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} @@ -402,7 +428,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -411,8 +437,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Markdown Linter" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/markdown-linter.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -423,7 +449,7 @@ jobs: echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false - name: Create gh-aw temp directory @@ -442,6 +468,7 @@ jobs: - name: Create cache-memory directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_cache_memory_dir.sh" - name: Restore cache-memory file share data + id: restore_cache_memory_0 uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: key: memory-none-nopolicy-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }} @@ -455,17 +482,10 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/setup_cache_memory_git.sh" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Checkout PR branch id: checkout-pr if: | @@ -481,11 +501,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) @@ -497,7 +517,7 @@ jobs: const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: activation path: /tmp/gh-aw @@ -517,15 +537,15 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_7bed7e7528e304cf_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_ea0ab9f0fa3ca9a2_EOF' {"create_issue":{"expires":48,"labels":["agentic-workflows","Area: Documentation"],"max":1,"title_prefix":"[linter] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_7bed7e7528e304cf_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_ea0ab9f0fa3ca9a2_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -656,55 +676,17 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); await main(); - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - - name: Start MCP Gateway id: start-mcp-gateway env: + GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }} GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }} GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }} GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -eo pipefail mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" @@ -730,19 +712,19 @@ jobs: * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; esac DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.25' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --name awmg-mcpg --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.3.30' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_c6fee03c27b97257_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_f014db59cae17bc3_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.1.2", + "container": "ghcr.io/github/github-mcp-server:v1.4.0", "env": { - "GITHUB_HOST": "\${GITHUB_SERVER_URL}", - "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_HOST": "${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", "GITHUB_TOOLSETS": "context,repos,issues,pull_requests" }, @@ -754,10 +736,27 @@ jobs: } }, "safeoutputs": { - "type": "http", - "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + "type": "stdio", + "container": "ghcr.io/github/gh-aw-node", + "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"], + "args": ["-w", "\${GITHUB_WORKSPACE}"], + "entrypoint": "sh", + "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], + "env": { + "DEBUG": "*", + "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", + "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", + "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", + "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", + "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", + "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", + "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", + "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", + "GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}", + "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", + "GITHUB_TOKEN": "\${GITHUB_TOKEN}", + "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", + "RUNNER_TEMP": "\${RUNNER_TEMP}" }, "guard-policies": { "write-sink": { @@ -775,7 +774,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_c6fee03c27b97257_EOF + GH_AW_MCP_CONFIG_f014db59cae17bc3_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -814,38 +813,43 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + GH_AW_CHROOT_BINARIES_SOURCE_PATH=/tmp/gh-aw GH_AW_CHROOT_IDENTITY_HOME=/tmp/gh-aw/home node "${RUNNER_TEMP}/gh-aw/actions/patch_awf_chroot_config.cjs" fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir /tmp/gh-aw/cache-memory/ --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir /tmp/gh-aw/cache-memory/ --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 15 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -860,6 +864,8 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Detect agent errors if: always() id: detect-agent-errors @@ -867,17 +873,10 @@ jobs: run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Copy Copilot session state files to logs if: always() continue-on-error: true @@ -901,8 +900,7 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); await main(); env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -1005,7 +1003,7 @@ jobs: GH_AW_CACHE_DIR: /tmp/gh-aw/cache-memory run: bash "${RUNNER_TEMP}/gh-aw/actions/check_cache_memory_git_integrity.sh" - name: Upload cache-memory data as artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 if: always() with: name: cache-memory @@ -1014,7 +1012,7 @@ jobs: - name: Upload agent artifacts if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: agent path: | @@ -1056,6 +1054,8 @@ jobs: group: "gh-aw-conclusion-markdown-linter" cancel-in-progress: false queue: max + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} @@ -1064,7 +1064,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1073,13 +1073,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Markdown Linter" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/markdown-linter.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1096,34 +1096,82 @@ jobs: run: | mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection echo "Usage artifact source file status:" - for file in /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do + for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file" done + [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true + [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl + mkdir -p /tmp/gh-aw/usage/activity + node ${{ runner.temp }}/gh-aw/actions/generate_usage_activity_summary.cjs find /tmp/gh-aw/usage -type f -print | sort - name: Upload usage artifact if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: usage path: | + /tmp/gh-aw/usage/aw_info.json /tmp/gh-aw/usage/aw-info.jsonl + /tmp/gh-aw/usage/agent_usage.json /tmp/gh-aw/usage/agent_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/github_rate_limits.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl + /tmp/gh-aw/usage/activity/summary.json if-no-files-found: ignore + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache-conclusion + if: always() + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-markdownlinter-${{ github.run_id }} + restore-keys: agentic-workflow-usage-markdownlinter- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Write daily AIC usage cache entry + id: write-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context); + const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs'); + await main(); + - name: Save daily AIC usage cache + id: save-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-markdownlinter-${{ github.run_id }} + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Upload daily AIC usage cache artifact + id: upload-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: aic-usage-cache + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + if-no-files-found: ignore + retention-days: 7 - name: Process no-op messages id: noop uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1206,7 +1254,6 @@ jobs: GH_AW_WORKFLOW_ID: "markdown-linter" GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" - GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} @@ -1230,6 +1277,8 @@ jobs: GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" GH_AW_TIMEOUT_MINUTES: "15" GH_AW_CACHE_MEMORY_ENABLED: "true" + GH_AW_CACHE_MEMORY_RESTORE_0_MATCHED_KEY: ${{ needs.agent.outputs.cache_memory_restore_0_matched_key || '' }} + GH_AW_CACHE_MEMORY_RESTORE_0_CACHE_HIT: ${{ needs.agent.outputs.cache_memory_restore_0_cache_hit || 'false' }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1242,11 +1291,13 @@ jobs: needs: - activation - agent - if: > - always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + if: always() && needs.agent.result != 'skipped' runs-on: ubuntu-latest permissions: contents: read + copilot-requests: write + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: aic: ${{ steps.parse_detection_token_usage.outputs.aic }} detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} @@ -1255,7 +1306,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1264,13 +1315,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Markdown Linter" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/markdown-linter.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1283,7 +1334,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Checkout repository for patch context if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false # --- Threat Detection --- @@ -1292,7 +1343,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d - name: Check if detection needed id: detection_guard if: always() @@ -1355,11 +1406,11 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1378,37 +1429,44 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + _GH_AW_CHROOT_JSON=$(jq -c --arg src /tmp/gh-aw --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home /tmp/gh-aw/home '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; } + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "/tmp/gh-aw/awf-config.json" fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1422,6 +1480,8 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Parse threat detection token usage for step summary id: parse_detection_token_usage if: always() @@ -1437,7 +1497,7 @@ jobs: await main(); - name: Upload threat detection log if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: detection path: /tmp/gh-aw/threat-detection/detection.log @@ -1496,7 +1556,8 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.60" + GH_AW_ENGINE_VERSION: "1.0.65" + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "markdown-linter" GH_AW_WORKFLOW_NAME: "Markdown Linter" @@ -1513,7 +1574,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1522,13 +1583,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Markdown Linter" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/markdown-linter.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1542,8 +1603,7 @@ jobs: - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash - # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. - run: | + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. GH_HOST="${GITHUB_SERVER_URL#https://}" @@ -1568,7 +1628,7 @@ jobs: await main(); - name: Upload Safe Outputs Items if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: safe-outputs-items path: | @@ -1588,8 +1648,7 @@ jobs: - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash - # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. - run: | + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. GH_HOST="${GITHUB_SERVER_URL#https://}" @@ -1640,11 +1699,12 @@ jobs: runs-on: ubuntu-slim permissions: {} env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: markdownlinter steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1653,12 +1713,12 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Markdown Linter" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/markdown-linter.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download cache-memory artifact (default) id: download_cache_default - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 continue-on-error: true with: name: cache-memory diff --git a/.github/workflows/markdown-linter.md b/.github/workflows/markdown-linter.md index e8858e1fb3..f999fe356f 100644 --- a/.github/workflows/markdown-linter.md +++ b/.github/workflows/markdown-linter.md @@ -8,6 +8,7 @@ permissions: contents: read issues: read pull-requests: read + copilot-requests: write imports: - shared/reporting.md safe-outputs: diff --git a/.github/workflows/md-link-checker.lock.yml b/.github/workflows/md-link-checker.lock.yml index 14a46941cb..11216fcf7c 100644 --- a/.github/workflows/md-link-checker.lock.yml +++ b/.github/workflows/md-link-checker.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"2e3cd055420dda0d28349dcdfaac918257386fa09455017f90cff3a9be45e265","body_hash":"e7dcb77fa7c30cdfcbf73bbd3bfe7249cb5a7b2726fb570f9bd4906d14bef348","compiler_version":"v0.79.8","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.60"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/setup-python","sha":"a309ff8b426b58ec0e2a45f0f869d46889d02405","version":"v6.2.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"c0338fef4749d08c21f8f975fb0e37efa17dda47","version":"v0.79.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]} -# This file was automatically generated by gh-aw (v0.79.8). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"65f354f865f4461958fbb15214f065584e91caa9af980f0b8ca7ecd5883dcc71","body_hash":"e7dcb77fa7c30cdfcbf73bbd3bfe7249cb5a7b2726fb570f9bd4906d14bef348","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} +# gh-aw-manifest: {"version":1,"secrets":["GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/setup-python","sha":"a309ff8b426b58ec0e2a45f0f869d46889d02405","version":"v6.2.0"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} +# This file was automatically generated by gh-aw (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -26,7 +26,6 @@ # Weekly automated link checker that finds and fixes broken links in documentation files # # Secrets used: -# - COPILOT_GITHUB_TOKEN # - GH_AW_CI_TRIGGER_TOKEN # - GH_AW_GITHUB_MCP_SERVER_TOKEN # - GH_AW_GITHUB_TOKEN @@ -36,21 +35,22 @@ # - actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 # - actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 # - actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 -# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 -# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 +# - actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 -# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 +# - actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 +# - github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 -# - ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa -# - ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c +# - ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d +# - ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d +# - ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be +# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b +# - ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 name: "Weekly Relative Link Checker & Fixer" on: @@ -80,6 +80,7 @@ jobs: contents: read env: GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: comment_id: "" comment_repo: "" @@ -89,7 +90,6 @@ jobs: engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} - secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} @@ -97,7 +97,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -105,8 +105,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Weekly Relative Link Checker & Fixer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/md-link-checker.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info @@ -114,16 +114,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AGENT_VERSION: "1.0.60" - GH_AW_INFO_CLI_VERSION: "v0.79.8" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AGENT_VERSION: "1.0.65" + GH_AW_INFO_CLI_VERSION: "v0.81.6" GH_AW_INFO_WORKFLOW_NAME: "Weekly Relative Link Checker & Fixer" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["github"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -134,6 +134,30 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-mdlinkchecker-${{ github.run_id }} + restore-keys: agentic-workflow-usage-mdlinkchecker- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Restore daily AIC usage cache (artifact fallback) + id: restore-daily-aic-cache-fallback + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_RESTORE_DAILY_AIC_CACHE_HIT: ${{ steps.restore-daily-aic-cache.outputs.cache-hit }} + GH_AW_RESTORE_DAILY_AIC_CACHE_MATCHED_KEY: ${{ steps.restore-daily-aic-cache.outputs.cache-matched-key }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs'); + await main(); - name: Check daily workflow token guardrail id: daily-effective-workflow-guardrail if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} @@ -143,6 +167,8 @@ jobs: GH_AW_WORKFLOW_ID: "md-link-checker" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} + GH_AW_HAS_SLASH_COMMAND: "false" + GH_AW_HAS_LABEL_COMMAND: "false" GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} with: @@ -152,13 +178,8 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs'); await main(); - - name: Validate COPILOT_GITHUB_TOKEN secret - id: validate-secret - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Checkout .github and .agents folders - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false sparse-checkout: | @@ -194,13 +215,16 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.79.8" + GH_AW_COMPILED_VERSION: "v0.81.6" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); await main(); + - name: Log runtime features + if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh" - name: Create prompt with built-in context env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt @@ -217,24 +241,24 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_a069b2ccabebbda9_EOF' + cat << 'GH_AW_PROMPT_71f4a0d11221e2fd_EOF' - GH_AW_PROMPT_a069b2ccabebbda9_EOF + GH_AW_PROMPT_71f4a0d11221e2fd_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_a069b2ccabebbda9_EOF' + cat << 'GH_AW_PROMPT_71f4a0d11221e2fd_EOF' Tools: create_issue, create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_a069b2ccabebbda9_EOF + GH_AW_PROMPT_71f4a0d11221e2fd_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_a069b2ccabebbda9_EOF' + cat << 'GH_AW_PROMPT_71f4a0d11221e2fd_EOF' - GH_AW_PROMPT_a069b2ccabebbda9_EOF + GH_AW_PROMPT_71f4a0d11221e2fd_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_a069b2ccabebbda9_EOF' + cat << 'GH_AW_PROMPT_71f4a0d11221e2fd_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -263,12 +287,12 @@ jobs: {{/if}} - GH_AW_PROMPT_a069b2ccabebbda9_EOF + GH_AW_PROMPT_71f4a0d11221e2fd_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_a069b2ccabebbda9_EOF' + cat << 'GH_AW_PROMPT_71f4a0d11221e2fd_EOF' {{#runtime-import .github/workflows/md-link-checker.md}} - GH_AW_PROMPT_a069b2ccabebbda9_EOF + GH_AW_PROMPT_71f4a0d11221e2fd_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -335,7 +359,7 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: activation include-hidden-files: true @@ -356,7 +380,23 @@ jobs: needs: activation if: needs.activation.outputs.daily_ai_credits_exceeded != 'true' runs-on: ubuntu-latest - permissions: read-all + permissions: + actions: read + attestations: read + checks: read + contents: read + copilot-requests: write + deployments: read + discussions: read + issues: read + models: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read + vulnerability-alerts: read concurrency: group: "gh-aw-copilot-${{ github.workflow }}" queue: max @@ -366,12 +406,15 @@ jobs: GH_AW_ASSETS_BRANCH: "" GH_AW_ASSETS_MAX_SIZE_KB: 0 GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: mdlinkchecker outputs: agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} ai_credits_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.ai_credits_rate_limit_error || 'false' }} aic: ${{ steps.parse-mcp-gateway.outputs.aic }} ambient_context: ${{ steps.parse-mcp-gateway.outputs.ambient_context }} + cache_memory_restore_0_cache_hit: ${{ steps.restore_cache_memory_0.outputs.cache-hit || 'false' }} + cache_memory_restore_0_matched_key: ${{ steps.restore_cache_memory_0.outputs.cache-matched-key || '' }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} @@ -388,7 +431,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -397,8 +440,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Weekly Relative Link Checker & Fixer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/md-link-checker.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -432,6 +475,7 @@ jobs: - name: Create cache-memory directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_cache_memory_dir.sh" - name: Restore cache-memory file share data + id: restore_cache_memory_0 uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: key: memory-none-nopolicy-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }} @@ -445,17 +489,10 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/setup_cache_memory_git.sh" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Checkout PR branch id: checkout-pr if: | @@ -471,11 +508,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) @@ -487,7 +524,7 @@ jobs: const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: activation path: /tmp/gh-aw @@ -507,15 +544,15 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_aceb88683349707c_EOF' - {"create_issue":{"labels":["Area: Documentation","agentic-workflows"],"max":1,"title_prefix":"[link-checker] "},"create_pull_request":{"draft":false,"if_no_changes":"warn","labels":["Area: Documentation","agentic-workflows"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"fallback-to-issue","title_prefix":"[link-checker] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_aceb88683349707c_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_f40492e38b9d7abb_EOF' + {"create_issue":{"labels":["Area: Documentation","agentic-workflows"],"max":1,"title_prefix":"[link-checker] "},"create_pull_request":{"draft":false,"if_no_changes":"warn","labels":["Area: Documentation","agentic-workflows"],"max":1,"max_patch_files":100,"max_patch_size":4096,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"fallback-to-issue","title_prefix":"[link-checker] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_f40492e38b9d7abb_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -688,55 +725,17 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); await main(); - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - - name: Start MCP Gateway id: start-mcp-gateway env: + GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }} GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }} GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }} GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -eo pipefail mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" @@ -762,19 +761,19 @@ jobs: * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; esac DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.25' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --name awmg-mcpg --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.3.30' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_c6fee03c27b97257_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_f014db59cae17bc3_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.1.2", + "container": "ghcr.io/github/github-mcp-server:v1.4.0", "env": { - "GITHUB_HOST": "\${GITHUB_SERVER_URL}", - "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_HOST": "${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", "GITHUB_TOOLSETS": "context,repos,issues,pull_requests" }, @@ -786,10 +785,27 @@ jobs: } }, "safeoutputs": { - "type": "http", - "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + "type": "stdio", + "container": "ghcr.io/github/gh-aw-node", + "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"], + "args": ["-w", "\${GITHUB_WORKSPACE}"], + "entrypoint": "sh", + "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], + "env": { + "DEBUG": "*", + "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", + "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", + "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", + "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", + "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", + "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", + "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", + "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", + "GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}", + "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", + "GITHUB_TOKEN": "\${GITHUB_TOKEN}", + "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", + "RUNNER_TEMP": "\${RUNNER_TEMP}" }, "guard-policies": { "write-sink": { @@ -807,7 +823,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_c6fee03c27b97257_EOF + GH_AW_MCP_CONFIG_f014db59cae17bc3_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -846,38 +862,43 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.githubusercontent.com\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"codeload.github.com\",\"docs.github.com\",\"github-cloud.githubusercontent.com\",\"github-cloud.s3.amazonaws.com\",\"github.blog\",\"github.com\",\"github.githubassets.com\",\"host.docker.internal\",\"lfs.github.com\",\"objects.githubusercontent.com\",\"patch-diff.githubusercontent.com\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.githubusercontent.com\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"codeload.github.com\",\"docs.github.com\",\"github-cloud.githubusercontent.com\",\"github-cloud.s3.amazonaws.com\",\"github.blog\",\"github.com\",\"github.githubassets.com\",\"host.docker.internal\",\"lfs.github.com\",\"objects.githubusercontent.com\",\"patch-diff.githubusercontent.com\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + GH_AW_CHROOT_BINARIES_SOURCE_PATH=/tmp/gh-aw GH_AW_CHROOT_IDENTITY_HOME=/tmp/gh-aw/home node "${RUNNER_TEMP}/gh-aw/actions/patch_awf_chroot_config.cjs" fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir /tmp/gh-aw/cache-memory/ --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir /tmp/gh-aw/cache-memory/ --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 60 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -892,6 +913,8 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Detect agent errors if: always() id: detect-agent-errors @@ -899,17 +922,10 @@ jobs: run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Copy Copilot session state files to logs if: always() continue-on-error: true @@ -933,8 +949,7 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); await main(); env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -1037,7 +1052,7 @@ jobs: GH_AW_CACHE_DIR: /tmp/gh-aw/cache-memory run: bash "${RUNNER_TEMP}/gh-aw/actions/check_cache_memory_git_integrity.sh" - name: Upload cache-memory data as artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 if: always() with: name: cache-memory @@ -1046,7 +1061,7 @@ jobs: - name: Upload agent artifacts if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: agent path: | @@ -1088,6 +1103,8 @@ jobs: group: "gh-aw-conclusion-md-link-checker" cancel-in-progress: false queue: max + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} @@ -1096,7 +1113,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1105,13 +1122,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Weekly Relative Link Checker & Fixer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/md-link-checker.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1128,34 +1145,82 @@ jobs: run: | mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection echo "Usage artifact source file status:" - for file in /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do + for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file" done + [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true + [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl + mkdir -p /tmp/gh-aw/usage/activity + node ${{ runner.temp }}/gh-aw/actions/generate_usage_activity_summary.cjs find /tmp/gh-aw/usage -type f -print | sort - name: Upload usage artifact if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: usage path: | + /tmp/gh-aw/usage/aw_info.json /tmp/gh-aw/usage/aw-info.jsonl + /tmp/gh-aw/usage/agent_usage.json /tmp/gh-aw/usage/agent_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/github_rate_limits.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl + /tmp/gh-aw/usage/activity/summary.json if-no-files-found: ignore + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache-conclusion + if: always() + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-mdlinkchecker-${{ github.run_id }} + restore-keys: agentic-workflow-usage-mdlinkchecker- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Write daily AIC usage cache entry + id: write-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context); + const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs'); + await main(); + - name: Save daily AIC usage cache + id: save-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-mdlinkchecker-${{ github.run_id }} + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Upload daily AIC usage cache artifact + id: upload-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: aic-usage-cache + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + if-no-files-found: ignore + retention-days: 7 - name: Process no-op messages id: noop uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1238,7 +1303,6 @@ jobs: GH_AW_WORKFLOW_ID: "md-link-checker" GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" - GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} @@ -1264,6 +1328,8 @@ jobs: GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" GH_AW_TIMEOUT_MINUTES: "60" GH_AW_CACHE_MEMORY_ENABLED: "true" + GH_AW_CACHE_MEMORY_RESTORE_0_MATCHED_KEY: ${{ needs.agent.outputs.cache_memory_restore_0_matched_key || '' }} + GH_AW_CACHE_MEMORY_RESTORE_0_CACHE_HIT: ${{ needs.agent.outputs.cache_memory_restore_0_cache_hit || 'false' }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1276,11 +1342,13 @@ jobs: needs: - activation - agent - if: > - always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + if: always() && needs.agent.result != 'skipped' runs-on: ubuntu-latest permissions: contents: read + copilot-requests: write + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: aic: ${{ steps.parse_detection_token_usage.outputs.aic }} detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} @@ -1289,7 +1357,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1298,13 +1366,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Weekly Relative Link Checker & Fixer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/md-link-checker.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1317,7 +1385,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Checkout repository for patch context if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false # --- Threat Detection --- @@ -1326,7 +1394,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d - name: Check if detection needed id: detection_guard if: always() @@ -1389,11 +1457,11 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1412,37 +1480,44 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + _GH_AW_CHROOT_JSON=$(jq -c --arg src /tmp/gh-aw --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home /tmp/gh-aw/home '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; } + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "/tmp/gh-aw/awf-config.json" fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1456,6 +1531,8 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Parse threat detection token usage for step summary id: parse_detection_token_usage if: always() @@ -1471,7 +1548,7 @@ jobs: await main(); - name: Upload threat detection log if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: detection path: /tmp/gh-aw/threat-detection/detection.log @@ -1531,7 +1608,8 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.60" + GH_AW_ENGINE_VERSION: "1.0.65" + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "md-link-checker" GH_AW_WORKFLOW_NAME: "Weekly Relative Link Checker & Fixer" @@ -1550,7 +1628,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1559,13 +1637,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Weekly Relative Link Checker & Fixer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/md-link-checker.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1578,55 +1656,27 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Download patch artifact continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ - - name: Extract base branch from agent output - id: extract-base-branch - if: steps.download-agent-output.outcome == 'success' - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/extract_base_branch_from_agent_output.cjs'); - await main(); - - name: Checkout repository (trusted default branch for comment events) - if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') && (github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - ref: ${{ github.event.repository.default_branch }} - token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - persist-credentials: false - fetch-depth: 1 - name: Checkout repository - if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') && github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: - ref: ${{ steps.extract-base-branch.outputs.base-branch || github.base_ref || github.event.pull_request.base.ref || github.ref_name || github.event.repository.default_branch }} + persist-credentials: true token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - persist-credentials: false - fetch-depth: 1 - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GIT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GIT_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash - # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. - run: | + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. GH_HOST="${GITHUB_SERVER_URL#https://}" @@ -1641,7 +1691,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,codeload.github.com,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,lfs.github.com,objects.githubusercontent.com,patch-diff.githubusercontent.com,raw.githubusercontent.com,registry.npmjs.org,telemetry.enterprise.githubcopilot.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"labels\":[\"Area: Documentation\",\"agentic-workflows\"],\"max\":1,\"title_prefix\":\"[link-checker] \"},\"create_pull_request\":{\"draft\":false,\"if_no_changes\":\"warn\",\"labels\":[\"Area: Documentation\",\"agentic-workflows\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"fallback-to-issue\",\"title_prefix\":\"[link-checker] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"labels\":[\"Area: Documentation\",\"agentic-workflows\"],\"max\":1,\"title_prefix\":\"[link-checker] \"},\"create_pull_request\":{\"draft\":false,\"if_no_changes\":\"warn\",\"labels\":[\"Area: Documentation\",\"agentic-workflows\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"fallback-to-issue\",\"title_prefix\":\"[link-checker] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -1652,7 +1702,7 @@ jobs: await main(); - name: Upload Safe Outputs Items if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: safe-outputs-items path: | @@ -1669,11 +1719,12 @@ jobs: runs-on: ubuntu-slim permissions: {} env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: mdlinkchecker steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1682,12 +1733,12 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Weekly Relative Link Checker & Fixer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/md-link-checker.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download cache-memory artifact (default) id: download_cache_default - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 continue-on-error: true with: name: cache-memory diff --git a/.github/workflows/md-link-checker.md b/.github/workflows/md-link-checker.md index 658cb17ce6..d0fe2f7f6f 100644 --- a/.github/workflows/md-link-checker.md +++ b/.github/workflows/md-link-checker.md @@ -2,7 +2,23 @@ description: Weekly automated link checker that finds and fixes broken links in documentation files on: schedule: weekly on Friday -permissions: read-all +permissions: + actions: read + attestations: read + checks: read + contents: read + copilot-requests: write + deployments: read + discussions: read + issues: read + models: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read + vulnerability-alerts: read timeout-minutes: 60 network: allowed: diff --git a/.github/workflows/msbuild-quality-review.lock.yml b/.github/workflows/msbuild-quality-review.lock.yml index 7aa400916d..e170288872 100644 --- a/.github/workflows/msbuild-quality-review.lock.yml +++ b/.github/workflows/msbuild-quality-review.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"50814bb65caece743961b4038a00adc7a2a94e5944a85de921f331bb2a235add","body_hash":"aa7008bc8f1f30b7adc7d01718871f7bb1b657b972b75cc22faffe6c4140a12e","compiler_version":"v0.79.8","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.60"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"c0338fef4749d08c21f8f975fb0e37efa17dda47","version":"v0.79.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]} -# This file was automatically generated by gh-aw (v0.79.8). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"5cccc7591239cc6bccc5588e9899e6136cbeff08862df10e956f209a78d3031d","body_hash":"aa7008bc8f1f30b7adc7d01718871f7bb1b657b972b75cc22faffe6c4140a12e","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} +# gh-aw-manifest: {"version":1,"secrets":["GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} +# This file was automatically generated by gh-aw (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -30,27 +30,30 @@ # - shared/msbuild-review-shared.md # # Secrets used: -# - COPILOT_GITHUB_TOKEN # - GH_AW_CI_TRIGGER_TOKEN # - GH_AW_GITHUB_MCP_SERVER_TOKEN # - GH_AW_GITHUB_TOKEN # - GITHUB_TOKEN # # Custom actions used: -# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 -# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 +# - actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 +# - actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 +# - actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 -# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 +# - actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 +# - github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 -# - ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa -# - ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c +# - ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d +# - ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d +# - ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be +# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b +# - ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 name: "MSBuild Quality Review" on: @@ -80,6 +83,7 @@ jobs: contents: read env: GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: comment_id: "" comment_repo: "" @@ -89,7 +93,6 @@ jobs: engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} - secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} @@ -97,7 +100,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -105,8 +108,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "MSBuild Quality Review" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/msbuild-quality-review.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info @@ -114,16 +117,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AGENT_VERSION: "1.0.60" - GH_AW_INFO_CLI_VERSION: "v0.79.8" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AGENT_VERSION: "1.0.65" + GH_AW_INFO_CLI_VERSION: "v0.81.6" GH_AW_INFO_WORKFLOW_NAME: "MSBuild Quality Review" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","dotnet"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -134,6 +137,30 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-msbuildqualityreview-${{ github.run_id }} + restore-keys: agentic-workflow-usage-msbuildqualityreview- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Restore daily AIC usage cache (artifact fallback) + id: restore-daily-aic-cache-fallback + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_RESTORE_DAILY_AIC_CACHE_HIT: ${{ steps.restore-daily-aic-cache.outputs.cache-hit }} + GH_AW_RESTORE_DAILY_AIC_CACHE_MATCHED_KEY: ${{ steps.restore-daily-aic-cache.outputs.cache-matched-key }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs'); + await main(); - name: Check daily workflow token guardrail id: daily-effective-workflow-guardrail if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} @@ -143,6 +170,8 @@ jobs: GH_AW_WORKFLOW_ID: "msbuild-quality-review" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} + GH_AW_HAS_SLASH_COMMAND: "false" + GH_AW_HAS_LABEL_COMMAND: "false" GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} with: @@ -152,13 +181,8 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs'); await main(); - - name: Validate COPILOT_GITHUB_TOKEN secret - id: validate-secret - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Checkout .github and .agents folders - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false sparse-checkout: | @@ -194,13 +218,16 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.79.8" + GH_AW_COMPILED_VERSION: "v0.81.6" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); await main(); + - name: Log runtime features + if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh" - name: Create prompt with built-in context env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt @@ -217,23 +244,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_41d91875d39ae15a_EOF' + cat << 'GH_AW_PROMPT_5062a0b28238c25c_EOF' - GH_AW_PROMPT_41d91875d39ae15a_EOF + GH_AW_PROMPT_5062a0b28238c25c_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_41d91875d39ae15a_EOF' + cat << 'GH_AW_PROMPT_5062a0b28238c25c_EOF' Tools: create_issue, create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_41d91875d39ae15a_EOF + GH_AW_PROMPT_5062a0b28238c25c_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_41d91875d39ae15a_EOF' + cat << 'GH_AW_PROMPT_5062a0b28238c25c_EOF' - GH_AW_PROMPT_41d91875d39ae15a_EOF + GH_AW_PROMPT_5062a0b28238c25c_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_41d91875d39ae15a_EOF' + cat << 'GH_AW_PROMPT_5062a0b28238c25c_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -262,13 +289,13 @@ jobs: {{/if}} - GH_AW_PROMPT_41d91875d39ae15a_EOF + GH_AW_PROMPT_5062a0b28238c25c_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_41d91875d39ae15a_EOF' + cat << 'GH_AW_PROMPT_5062a0b28238c25c_EOF' {{#runtime-import .github/workflows/shared/msbuild-review-shared.md}} {{#runtime-import .github/workflows/msbuild-quality-review.md}} - GH_AW_PROMPT_41d91875d39ae15a_EOF + GH_AW_PROMPT_5062a0b28238c25c_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -328,7 +355,7 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: activation include-hidden-files: true @@ -351,6 +378,7 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + copilot-requests: write issues: read pull-requests: read concurrency: @@ -362,6 +390,7 @@ jobs: GH_AW_ASSETS_BRANCH: "" GH_AW_ASSETS_MAX_SIZE_KB: 0 GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: msbuildqualityreview outputs: agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} @@ -384,7 +413,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -393,8 +422,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "MSBuild Quality Review" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/msbuild-quality-review.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -405,7 +434,7 @@ jobs: echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false - name: Create gh-aw temp directory @@ -416,17 +445,10 @@ jobs: GH_TOKEN: ${{ github.token }} - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Checkout PR branch id: checkout-pr if: | @@ -442,11 +464,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) @@ -458,7 +480,7 @@ jobs: const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: activation path: /tmp/gh-aw @@ -478,15 +500,15 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_de471148ccec20f6_EOF' - {"create_issue":{"expires":168,"labels":["agentic-workflows","Area: Engineering"],"max":1,"title_prefix":"[msbuild-quality] "},"create_pull_request":{"draft":true,"labels":["agentic-workflows","Area: Engineering"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"fallback-to-issue","title_prefix":"[msbuild-quality] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_de471148ccec20f6_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_a424a472800ad534_EOF' + {"create_issue":{"expires":168,"labels":["agentic-workflows","Area: Engineering"],"max":1,"title_prefix":"[msbuild-quality] "},"create_pull_request":{"draft":true,"labels":["agentic-workflows","Area: Engineering"],"max":1,"max_patch_files":100,"max_patch_size":4096,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"fallback-to-issue","title_prefix":"[msbuild-quality] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_a424a472800ad534_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -659,55 +681,17 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); await main(); - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - - name: Start MCP Gateway id: start-mcp-gateway env: + GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }} GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }} GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }} GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -eo pipefail mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" @@ -733,19 +717,19 @@ jobs: * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; esac DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.25' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --name awmg-mcpg --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.3.30' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_b23263170a8acbca_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_5b7595fbaa07355e_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.1.2", + "container": "ghcr.io/github/github-mcp-server:v1.4.0", "env": { - "GITHUB_HOST": "\${GITHUB_SERVER_URL}", - "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_HOST": "${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", "GITHUB_TOOLSETS": "issues,repos" }, @@ -757,10 +741,27 @@ jobs: } }, "safeoutputs": { - "type": "http", - "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + "type": "stdio", + "container": "ghcr.io/github/gh-aw-node", + "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"], + "args": ["-w", "\${GITHUB_WORKSPACE}"], + "entrypoint": "sh", + "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], + "env": { + "DEBUG": "*", + "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", + "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", + "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", + "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", + "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", + "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", + "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", + "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", + "GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}", + "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", + "GITHUB_TOKEN": "\${GITHUB_TOKEN}", + "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", + "RUNNER_TEMP": "\${RUNNER_TEMP}" }, "guard-policies": { "write-sink": { @@ -778,7 +779,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_b23263170a8acbca_EOF + GH_AW_MCP_CONFIG_5b7595fbaa07355e_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -845,38 +846,43 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.vsblob.vsassets.io\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.nuget.org\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"azuresearch-usnc.nuget.org\",\"azuresearch-ussc.nuget.org\",\"builds.dotnet.microsoft.com\",\"ci.dot.net\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"dc.services.visualstudio.com\",\"dist.nuget.org\",\"dot.net\",\"dotnet.microsoft.com\",\"dotnetcli.blob.core.windows.net\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"nuget.org\",\"nuget.pkg.github.com\",\"nugetregistryv2prod.blob.core.windows.net\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"oneocsp.microsoft.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"pkgs.dev.azure.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\",\"www.microsoft.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.vsblob.vsassets.io\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.nuget.org\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"azuresearch-usnc.nuget.org\",\"azuresearch-ussc.nuget.org\",\"builds.dotnet.microsoft.com\",\"ci.dot.net\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"dc.services.visualstudio.com\",\"dist.nuget.org\",\"dot.net\",\"dotnet.microsoft.com\",\"dotnetcli.blob.core.windows.net\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"nuget.org\",\"nuget.pkg.github.com\",\"nugetregistryv2prod.blob.core.windows.net\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"oneocsp.microsoft.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"pkgs.dev.azure.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\",\"www.microsoft.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + GH_AW_CHROOT_BINARIES_SOURCE_PATH=/tmp/gh-aw GH_AW_CHROOT_IDENTITY_HOME=/tmp/gh-aw/home node "${RUNNER_TEMP}/gh-aw/actions/patch_awf_chroot_config.cjs" fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(find)'\'' --allow-tool '\''shell(git add:*)'\'' --allow-tool '\''shell(git branch:*)'\'' --allow-tool '\''shell(git checkout:*)'\'' --allow-tool '\''shell(git commit:*)'\'' --allow-tool '\''shell(git merge:*)'\'' --allow-tool '\''shell(git rm:*)'\'' --allow-tool '\''shell(git status)'\'' --allow-tool '\''shell(git switch:*)'\'' --allow-tool '\''shell(git:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(printf)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(safeoutputs:*)'\'' --allow-tool '\''shell(sed)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(find)'\'' --allow-tool '\''shell(git add:*)'\'' --allow-tool '\''shell(git branch:*)'\'' --allow-tool '\''shell(git checkout:*)'\'' --allow-tool '\''shell(git commit:*)'\'' --allow-tool '\''shell(git merge:*)'\'' --allow-tool '\''shell(git rm:*)'\'' --allow-tool '\''shell(git status)'\'' --allow-tool '\''shell(git switch:*)'\'' --allow-tool '\''shell(git:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(printf)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(safeoutputs:*)'\'' --allow-tool '\''shell(sed)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 30 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -891,6 +897,8 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Detect agent errors if: always() id: detect-agent-errors @@ -898,17 +906,10 @@ jobs: run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Copy Copilot session state files to logs if: always() continue-on-error: true @@ -932,8 +933,7 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); await main(); env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -1027,7 +1027,7 @@ jobs: - name: Upload agent artifacts if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: agent path: | @@ -1068,6 +1068,8 @@ jobs: group: "gh-aw-conclusion-msbuild-quality-review" cancel-in-progress: false queue: max + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} @@ -1076,7 +1078,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1085,13 +1087,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "MSBuild Quality Review" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/msbuild-quality-review.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1108,34 +1110,82 @@ jobs: run: | mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection echo "Usage artifact source file status:" - for file in /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do + for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file" done + [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true + [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl + mkdir -p /tmp/gh-aw/usage/activity + node ${{ runner.temp }}/gh-aw/actions/generate_usage_activity_summary.cjs find /tmp/gh-aw/usage -type f -print | sort - name: Upload usage artifact if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: usage path: | + /tmp/gh-aw/usage/aw_info.json /tmp/gh-aw/usage/aw-info.jsonl + /tmp/gh-aw/usage/agent_usage.json /tmp/gh-aw/usage/agent_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/github_rate_limits.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl + /tmp/gh-aw/usage/activity/summary.json + if-no-files-found: ignore + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache-conclusion + if: always() + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-msbuildqualityreview-${{ github.run_id }} + restore-keys: agentic-workflow-usage-msbuildqualityreview- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Write daily AIC usage cache entry + id: write-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context); + const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs'); + await main(); + - name: Save daily AIC usage cache + id: save-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-msbuildqualityreview-${{ github.run_id }} + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Upload daily AIC usage cache artifact + id: upload-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: aic-usage-cache + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl if-no-files-found: ignore + retention-days: 7 - name: Process no-op messages id: noop uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1218,7 +1268,6 @@ jobs: GH_AW_WORKFLOW_ID: "msbuild-quality-review" GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" - GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} @@ -1255,11 +1304,13 @@ jobs: needs: - activation - agent - if: > - always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + if: always() && needs.agent.result != 'skipped' runs-on: ubuntu-latest permissions: contents: read + copilot-requests: write + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: aic: ${{ steps.parse_detection_token_usage.outputs.aic }} detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} @@ -1268,7 +1319,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1277,13 +1328,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "MSBuild Quality Review" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/msbuild-quality-review.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1296,7 +1347,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Checkout repository for patch context if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false # --- Threat Detection --- @@ -1305,7 +1356,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d - name: Check if detection needed id: detection_guard if: always() @@ -1368,11 +1419,11 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1391,37 +1442,44 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + _GH_AW_CHROOT_JSON=$(jq -c --arg src /tmp/gh-aw --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home /tmp/gh-aw/home '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; } + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "/tmp/gh-aw/awf-config.json" fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1435,6 +1493,8 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Parse threat detection token usage for step summary id: parse_detection_token_usage if: always() @@ -1450,7 +1510,7 @@ jobs: await main(); - name: Upload threat detection log if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: detection path: /tmp/gh-aw/threat-detection/detection.log @@ -1510,7 +1570,8 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.60" + GH_AW_ENGINE_VERSION: "1.0.65" + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "msbuild-quality-review" GH_AW_WORKFLOW_NAME: "MSBuild Quality Review" @@ -1529,7 +1590,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1538,13 +1599,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "MSBuild Quality Review" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/msbuild-quality-review.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1557,55 +1618,27 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Download patch artifact continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ - - name: Extract base branch from agent output - id: extract-base-branch - if: steps.download-agent-output.outcome == 'success' - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/extract_base_branch_from_agent_output.cjs'); - await main(); - - name: Checkout repository (trusted default branch for comment events) - if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') && (github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - ref: ${{ github.event.repository.default_branch }} - token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - persist-credentials: false - fetch-depth: 1 - name: Checkout repository - if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') && github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: - ref: ${{ steps.extract-base-branch.outputs.base-branch || github.base_ref || github.event.pull_request.base.ref || github.ref_name || github.event.repository.default_branch }} + persist-credentials: true token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - persist-credentials: false - fetch-depth: 1 - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GIT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GIT_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash - # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. - run: | + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. GH_HOST="${GITHUB_SERVER_URL#https://}" @@ -1620,7 +1653,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"expires\":168,\"labels\":[\"agentic-workflows\",\"Area: Engineering\"],\"max\":1,\"title_prefix\":\"[msbuild-quality] \"},\"create_pull_request\":{\"draft\":true,\"labels\":[\"agentic-workflows\",\"Area: Engineering\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"fallback-to-issue\",\"title_prefix\":\"[msbuild-quality] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"expires\":168,\"labels\":[\"agentic-workflows\",\"Area: Engineering\"],\"max\":1,\"title_prefix\":\"[msbuild-quality] \"},\"create_pull_request\":{\"draft\":true,\"labels\":[\"agentic-workflows\",\"Area: Engineering\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"fallback-to-issue\",\"title_prefix\":\"[msbuild-quality] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -1631,7 +1664,7 @@ jobs: await main(); - name: Upload Safe Outputs Items if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: safe-outputs-items path: | diff --git a/.github/workflows/msbuild-quality-review.md b/.github/workflows/msbuild-quality-review.md index 7734f591f3..6e8608cc4a 100644 --- a/.github/workflows/msbuild-quality-review.md +++ b/.github/workflows/msbuild-quality-review.md @@ -15,6 +15,7 @@ permissions: contents: read issues: read pull-requests: read + copilot-requests: write imports: - shared/msbuild-review-shared.md diff --git a/.github/workflows/pr-expert-reviewer.lock.yml b/.github/workflows/pr-expert-reviewer.lock.yml index 39b3476a8b..edb98a2204 100644 --- a/.github/workflows/pr-expert-reviewer.lock.yml +++ b/.github/workflows/pr-expert-reviewer.lock.yml @@ -1,5 +1,7 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"55a2266988f4c741701060ac6bda2c3d22affa3a5c0cfa4a863486b28b147e60","compiler_version":"v0.72.1","strict":true,"agent_id":"copilot"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"bc56a0cad2f450c562810785ef38649c04db812a","version":"v0.72.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"5d2148e8d51160eb692da7bf9578e5a969d3a85173f40d313a923e5cb36012ae","body_hash":"b0704edeac6e84cfb0af3313a0dd930a253cf67aef94ebf9d0a8ab0f96085262","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} +# gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} +# This file was automatically generated by gh-aw (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -14,7 +16,6 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.72.1). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -29,7 +30,6 @@ # - shared/repo-build-setup.md # # Secrets used: -# - COPILOT_GITHUB_TOKEN # - GH_AW_GITHUB_MCP_SERVER_TOKEN # - GH_AW_GITHUB_TOKEN # - GITHUB_TOKEN @@ -37,23 +37,24 @@ # Custom actions used: # - actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 # - actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 -# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 -# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 +# - actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 -# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 +# - actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 +# - github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.25.41 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41 -# - ghcr.io/github/gh-aw-firewall/squid:0.25.41 -# - ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c -# - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 -# - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f +# - ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d +# - ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d +# - ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be +# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b +# - ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 name: "Expert Code Reviewer 🧠" -"on": +on: pull_request: types: - opened @@ -64,7 +65,7 @@ name: "Expert Code Reviewer 🧠" inputs: aw_context: default: "" - description: Agent caller context (used internally by Agentic Workflows). + description: "Agent caller context (used internally by Agentic Workflows)." required: false type: string @@ -85,14 +86,21 @@ jobs: permissions: actions: read contents: read + env: + GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: "" comment_repo: "" + daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }} + daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }} + daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }} engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} - secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} text: ${{ steps.sanitized.outputs.text }} @@ -100,37 +108,38 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }} + safe-output-artifact-client: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} env: GH_AW_SETUP_WORKFLOW_NAME: "Expert Code Reviewer 🧠" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/pr-expert-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.40" - GH_AW_INFO_AGENT_VERSION: "1.0.40" - GH_AW_INFO_CLI_VERSION: "v0.72.1" + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AGENT_VERSION: "1.0.65" + GH_AW_INFO_CLI_VERSION: "v0.81.6" GH_AW_INFO_WORKFLOW_NAME: "Expert Code Reviewer 🧠" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.41" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" - GITHUB_MCP_LOCKDOWN_EXPLICIT: "true" - GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} - GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | @@ -138,18 +147,58 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); - - name: Validate COPILOT_GITHUB_TOKEN secret - id: validate-secret - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-prexpertreviewer-${{ github.run_id }} + restore-keys: agentic-workflow-usage-prexpertreviewer- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Restore daily AIC usage cache (artifact fallback) + id: restore-daily-aic-cache-fallback + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_RESTORE_DAILY_AIC_CACHE_HIT: ${{ steps.restore-daily-aic-cache.outputs.cache-hit }} + GH_AW_RESTORE_DAILY_AIC_CACHE_MATCHED_KEY: ${{ steps.restore-daily-aic-cache.outputs.cache-matched-key }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs'); + await main(); + - name: Check daily workflow token guardrail + id: daily-effective-workflow-guardrail + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_WORKFLOW_NAME: "Expert Code Reviewer 🧠" + GH_AW_WORKFLOW_ID: "pr-expert-reviewer" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} + GH_AW_HAS_SLASH_COMMAND: "false" + GH_AW_HAS_LABEL_COMMAND: "false" + GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs'); + await main(); - name: Checkout .github and .agents folders - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false sparse-checkout: | .github .agents + .antigravity .claude .codex .crush @@ -160,8 +209,8 @@ jobs: fetch-depth: 1 - name: Save agent config folders for base branch restoration env: - GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" - GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" # poutine:ignore untrusted_checkout_exec run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" - name: Check workflow lock file @@ -179,7 +228,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.72.1" + GH_AW_COMPILED_VERSION: "v0.81.6" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -197,14 +246,18 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/compute_text.cjs'); await main(); + - name: Log runtime features + if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh" - name: Create prompt with built-in context env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GH_AW_GITHUB_EVENT_PULL_REQUEST_TITLE: ${{ github.event.pull_request.title }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} @@ -214,56 +267,56 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_18ae83114f27f0a4_EOF' + cat << 'GH_AW_PROMPT_a68fa288f6217cc2_EOF' - GH_AW_PROMPT_18ae83114f27f0a4_EOF + GH_AW_PROMPT_a68fa288f6217cc2_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_18ae83114f27f0a4_EOF' + cat << 'GH_AW_PROMPT_a68fa288f6217cc2_EOF' Tools: create_pull_request_review_comment(max:5), submit_pull_request_review, missing_tool, missing_data, noop - GH_AW_PROMPT_18ae83114f27f0a4_EOF + GH_AW_PROMPT_a68fa288f6217cc2_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_18ae83114f27f0a4_EOF' + cat << 'GH_AW_PROMPT_a68fa288f6217cc2_EOF' The following GitHub context information is available for this workflow: - {{#if __GH_AW_GITHUB_ACTOR__ }} + {{#if github.actor}} - **actor**: __GH_AW_GITHUB_ACTOR__ {{/if}} - {{#if __GH_AW_GITHUB_REPOSITORY__ }} + {{#if github.repository}} - **repository**: __GH_AW_GITHUB_REPOSITORY__ {{/if}} - {{#if __GH_AW_GITHUB_WORKSPACE__ }} + {{#if github.workspace}} - **workspace**: __GH_AW_GITHUB_WORKSPACE__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} - - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}} + - **issue-number**: #__GH_AW_EXPR_802A9F6A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} - - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}} + - **discussion-number**: #__GH_AW_EXPR_1A3A194A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} - - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}} + - **pull-request-number**: #__GH_AW_EXPR_463A214A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} - - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{#if github.event.comment.id || github.aw.context.comment_id}} + - **comment-id**: __GH_AW_EXPR_FF1D34CE__ {{/if}} - {{#if __GH_AW_GITHUB_RUN_ID__ }} + {{#if github.run_id}} - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ {{/if}} - GH_AW_PROMPT_18ae83114f27f0a4_EOF + GH_AW_PROMPT_a68fa288f6217cc2_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_18ae83114f27f0a4_EOF' + cat << 'GH_AW_PROMPT_a68fa288f6217cc2_EOF' {{#runtime-import .github/workflows/shared/repo-build-setup.md}} {{#runtime-import .github/workflows/pr-expert-reviewer.md}} - GH_AW_PROMPT_18ae83114f27f0a4_EOF + GH_AW_PROMPT_a68fa288f6217cc2_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -287,10 +340,11 @@ jobs: GH_AW_ALLOWED_EXTENSIONS: '' GH_AW_CACHE_DESCRIPTION: '' GH_AW_CACHE_DIR: '/tmp/gh-aw/cache-memory/' + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GH_AW_GITHUB_EVENT_PULL_REQUEST_TITLE: ${{ github.event.pull_request.title }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} @@ -312,10 +366,11 @@ jobs: GH_AW_ALLOWED_EXTENSIONS: process.env.GH_AW_ALLOWED_EXTENSIONS, GH_AW_CACHE_DESCRIPTION: process.env.GH_AW_CACHE_DESCRIPTION, GH_AW_CACHE_DIR: process.env.GH_AW_CACHE_DIR, + GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A, + GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A, + GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A, + GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE, GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, - GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, GH_AW_GITHUB_EVENT_PULL_REQUEST_TITLE: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_TITLE, GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, @@ -337,27 +392,31 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: activation include-hidden-files: true path: | /tmp/gh-aw/aw_info.json + /tmp/gh-aw/models.json /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/aw-prompts/prompt-template.txt /tmp/gh-aw/aw-prompts/prompt-import-tree.json /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/base /tmp/gh-aw/.github/agents + /tmp/gh-aw/.github/skills if-no-files-found: ignore retention-days: 1 agent: needs: activation + if: needs.activation.outputs.daily_ai_credits_exceeded != 'true' runs-on: ubuntu-latest permissions: actions: read contents: read + copilot-requests: write pull-requests: read env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} @@ -365,31 +424,43 @@ jobs: GH_AW_ASSETS_BRANCH: "" GH_AW_ASSETS_MAX_SIZE_KB: 0 GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: prexpertreviewer outputs: - agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} + agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} + ai_credits_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.ai_credits_rate_limit_error || 'false' }} + aic: ${{ steps.parse-mcp-gateway.outputs.aic }} + ambient_context: ${{ steps.parse-mcp-gateway.outputs.ambient_context }} + cache_memory_restore_0_cache_hit: ${{ steps.restore_cache_memory_0.outputs.cache-hit || 'false' }} + cache_memory_restore_0_matched_key: ${{ steps.restore_cache_memory_0.outputs.cache-matched-key || '' }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} - inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} - mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} + inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} model: ${{ needs.activation.outputs.model }} - model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} + model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} output: ${{ steps.collect_output.outputs.output }} output_types: ${{ steps.collect_output.outputs.output_types }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} + unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Expert Code Reviewer 🧠" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/pr-expert-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths run: | @@ -399,7 +470,7 @@ jobs: echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false - name: Create gh-aw temp directory @@ -412,6 +483,7 @@ jobs: - name: Create cache-memory directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_cache_memory_dir.sh" - name: Restore cache-memory file share data + id: restore_cache_memory_0 uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: key: memory-none-9f0b69b3-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }} @@ -425,21 +497,14 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/setup_cache_memory_git.sh" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Checkout PR branch id: checkout-pr if: | - github.event.pull_request || github.event.issue.pull_request + github.event.pull_request || github.event.issue.pull_request || github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -451,11 +516,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Parse integrity filter lists id: parse-guard-vars env: @@ -464,31 +529,35 @@ jobs: GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: activation path: /tmp/gh-aw - name: Restore agent config folders from base branch if: steps.checkout-pr.outcome == 'success' env: - GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" - GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" - name: Restore inline sub-agents from activation artifact env: GH_AW_SUB_AGENT_DIR: ".github/agents" GH_AW_SUB_AGENT_EXT: ".agent.md" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" + - name: Restore inline skills from activation artifact + env: + GH_AW_SKILL_DIR: ".github/skills" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41 ghcr.io/github/gh-aw-firewall/squid:0.25.41 ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_4c86a60e9ab81882_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_0919be59eb403ee5_EOF' {"create_pull_request_review_comment":{"max":5,"side":"RIGHT"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{},"submit_pull_request_review":{"allowed_events":["COMMENT","REQUEST_CHANGES"],"max":1}} - GH_AW_SAFE_OUTPUTS_CONFIG_4c86a60e9ab81882_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_0919be59eb403ee5_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -627,6 +696,13 @@ jobs: "REQUEST_CHANGES", "COMMENT" ] + }, + "pull_request_number": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } } @@ -638,53 +714,15 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); await main(); - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - - name: Start MCP Gateway id: start-mcp-gateway env: + GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }} GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -eo pipefail mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" @@ -704,21 +742,25 @@ jobs: export GH_AW_ENGINE="copilot" MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') - DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.6' + case "${DOCKER_HOST:-}" in + unix://* ) DOCKER_SOCK_PATH="${DOCKER_HOST#unix://}" ;; + /* ) DOCKER_SOCK_PATH="$DOCKER_HOST" ;; + * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; + esac + DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --name awmg-mcpg --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.3.30' - mkdir -p /home/runner/.copilot + mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_eea62c21b9eba9d2_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_4d258df65c95c3f3_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.0.3", + "container": "ghcr.io/github/github-mcp-server:v1.4.0", "env": { - "GITHUB_HOST": "\${GITHUB_SERVER_URL}", - "GITHUB_LOCKDOWN_MODE": "1", - "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_HOST": "${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", "GITHUB_TOOLSETS": "pull_requests,repos" }, @@ -733,10 +775,27 @@ jobs: } }, "safeoutputs": { - "type": "http", - "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + "type": "stdio", + "container": "ghcr.io/github/gh-aw-node", + "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"], + "args": ["-w", "\${GITHUB_WORKSPACE}"], + "entrypoint": "sh", + "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], + "env": { + "DEBUG": "*", + "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", + "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", + "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", + "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", + "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", + "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", + "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", + "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", + "GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}", + "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", + "GITHUB_TOKEN": "\${GITHUB_TOKEN}", + "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", + "RUNNER_TEMP": "\${RUNNER_TEMP}" }, "guard-policies": { "write-sink": { @@ -754,7 +813,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_eea62c21b9eba9d2_EOF + GH_AW_MCP_CONFIG_4d258df65c95c3f3_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -782,25 +841,54 @@ jobs: timeout-minutes: 15 run: | set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + mkdir -p "$HOME/.copilot" + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" + export XDG_CONFIG_HOME="$HOME" + export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json" touch /tmp/gh-aw/agent-step-summary.md GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.41/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","github.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com"]},"apiProxy":{"enabled":true,"models":{"auto":["large"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"]}},"container":{"imageTag":"0.25.41"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir /tmp/gh-aw/cache-memory/ --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + GH_AW_CHROOT_BINARIES_SOURCE_PATH=/tmp/gh-aw GH_AW_CHROOT_IDENTITY_HOME=/tmp/gh-aw/home node "${RUNNER_TEMP}/gh-aw/actions/patch_awf_chroot_config.cjs" + fi + GH_AW_TOOL_CACHE_MOUNT="" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" + if [ -d "$GH_AW_TOOL_CACHE" ]; then + if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then + GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" + fi + fi + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir /tmp/gh-aw/cache-memory/ --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_API_KEY: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ github.token }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} + GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.72.1 + GH_AW_TIMEOUT_MINUTES: 15 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -814,25 +902,20 @@ jobs: GIT_AUTHOR_NAME: github-actions[bot] GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] - XDG_CONFIG_HOME: /home/runner - - name: Detect Copilot errors - id: detect-copilot-errors + RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} + - name: Detect agent errors if: always() + id: detect-agent-errors continue-on-error: true - run: node "${RUNNER_TEMP}/gh-aw/actions/detect_copilot_errors.cjs" + run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Copy Copilot session state files to logs if: always() continue-on-error: true @@ -856,8 +939,7 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); await main(); env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -953,16 +1035,23 @@ jobs: env: GH_AW_CACHE_DIR: /tmp/gh-aw/cache-memory run: bash "${RUNNER_TEMP}/gh-aw/actions/commit_cache_memory_git.sh" + - name: Check cache-memory git integrity + if: always() + continue-on-error: true + env: + GH_AW_CACHE_DIR: /tmp/gh-aw/cache-memory + run: bash "${RUNNER_TEMP}/gh-aw/actions/check_cache_memory_git_integrity.sh" - name: Upload cache-memory data as artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 if: always() with: name: cache-memory + include-hidden-files: true path: /tmp/gh-aw/cache-memory - name: Upload agent artifacts if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: agent path: | @@ -996,7 +1085,7 @@ jobs: - update_cache_memory if: > always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || - needs.activation.outputs.stale_lock_file_failed == 'true') + needs.activation.outputs.stale_lock_file_failed == 'true' || needs.activation.outputs.daily_ai_credits_exceeded == 'true') runs-on: ubuntu-slim permissions: contents: read @@ -1004,6 +1093,9 @@ jobs: concurrency: group: "gh-aw-conclusion-pr-expert-reviewer" cancel-in-progress: false + queue: max + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} @@ -1012,19 +1104,22 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Expert Code Reviewer 🧠" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/pr-expert-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1035,6 +1130,88 @@ jobs: mkdir -p /tmp/gh-aw/ find "/tmp/gh-aw/" -type f -print echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Collect usage artifact files + if: always() + continue-on-error: true + run: | + mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection + echo "Usage artifact source file status:" + for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do + [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file" + done + [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true + [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true + [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true + [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true + [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true + [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl + [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl + mkdir -p /tmp/gh-aw/usage/activity + node ${{ runner.temp }}/gh-aw/actions/generate_usage_activity_summary.cjs + find /tmp/gh-aw/usage -type f -print | sort + - name: Upload usage artifact + if: always() + continue-on-error: true + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: usage + path: | + /tmp/gh-aw/usage/aw_info.json + /tmp/gh-aw/usage/aw-info.jsonl + /tmp/gh-aw/usage/agent_usage.json + /tmp/gh-aw/usage/agent_usage.jsonl + /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/github_rate_limits.jsonl + /tmp/gh-aw/usage/agent/token_usage.jsonl + /tmp/gh-aw/usage/detection/token_usage.jsonl + /tmp/gh-aw/usage/activity/summary.json + if-no-files-found: ignore + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache-conclusion + if: always() + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-prexpertreviewer-${{ github.run_id }} + restore-keys: agentic-workflow-usage-prexpertreviewer- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Write daily AIC usage cache entry + id: write-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context); + const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs'); + await main(); + - name: Save daily AIC usage cache + id: save-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-prexpertreviewer-${{ github.run_id }} + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Upload daily AIC usage cache artifact + id: upload-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: aic-usage-cache + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + if-no-files-found: ignore + retention-days: 7 - name: Process no-op messages id: noop uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1042,9 +1219,14 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" GH_AW_WORKFLOW_NAME: "Expert Code Reviewer 🧠" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/pr-expert-reviewer.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_NOOP_REPORT_AS_ISSUE: "false" + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} + GH_AW_WORKFLOW_ID: "pr-expert-reviewer" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1058,6 +1240,7 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "Expert Code Reviewer 🧠" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/pr-expert-reviewer.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} @@ -1075,6 +1258,7 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" GH_AW_WORKFLOW_NAME: "Expert Code Reviewer 🧠" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/pr-expert-reviewer.md" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1089,6 +1273,7 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" GH_AW_WORKFLOW_NAME: "Expert Code Reviewer 🧠" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/pr-expert-reviewer.md" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1103,13 +1288,19 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "Expert Code Reviewer 🧠" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/pr-expert-reviewer.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_WORKFLOW_ID: "pr-expert-reviewer" GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" - GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} + GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} + GH_AW_UNKNOWN_MODEL_AI_CREDITS: ${{ needs.agent.outputs.unknown_model_ai_credits || 'false' }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} @@ -1117,6 +1308,9 @@ jobs: GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_DAILY_AI_CREDITS_EXCEEDED: ${{ needs.activation.outputs.daily_ai_credits_exceeded }} + GH_AW_DAILY_AI_CREDITS_TOTAL_EFFECTIVE_TOKENS: ${{ needs.activation.outputs.daily_ai_credits_total_effective_tokens }} + GH_AW_DAILY_AI_CREDITS_THRESHOLD: ${{ needs.activation.outputs.daily_ai_credits_threshold }} GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🧠 *Reviewed by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔎 [{workflow_name}]({run_url}) is analyzing this PR for correctness, performance, and safety issues...\",\"runSuccess\":\"🧠 Analysis complete. [{workflow_name}]({run_url}) has finished the expert review. ✅\",\"runFailure\":\"⚠️ [{workflow_name}]({run_url}) {status}. Expert review could not be completed.\"}" GH_AW_GROUP_REPORTS: "false" GH_AW_FAILURE_REPORT_AS_ISSUE: "true" @@ -1124,6 +1318,8 @@ jobs: GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" GH_AW_TIMEOUT_MINUTES: "15" GH_AW_CACHE_MEMORY_ENABLED: "true" + GH_AW_CACHE_MEMORY_RESTORE_0_MATCHED_KEY: ${{ needs.agent.outputs.cache_memory_restore_0_matched_key || '' }} + GH_AW_CACHE_MEMORY_RESTORE_0_CACHE_HIT: ${{ needs.agent.outputs.cache_memory_restore_0_cache_hit || 'false' }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1136,31 +1332,37 @@ jobs: needs: - activation - agent - if: > - always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + if: always() && needs.agent.result != 'skipped' runs-on: ubuntu-latest permissions: contents: read + copilot-requests: write + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: + aic: ${{ steps.parse_detection_token_usage.outputs.aic }} detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} detection_reason: ${{ steps.detection_conclusion.outputs.reason }} detection_success: ${{ steps.detection_conclusion.outputs.success }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Expert Code Reviewer 🧠" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/pr-expert-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1173,7 +1375,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Checkout repository for patch context if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false # --- Threat Detection --- @@ -1182,7 +1384,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41 ghcr.io/github/gh-aw-firewall/squid:0.25.41 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d - name: Check if detection needed id: detection_guard if: always() @@ -1201,13 +1403,17 @@ jobs: if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" - rm -f /home/runner/.copilot/mcp-config.json + rm -f "$HOME/.copilot/mcp-config.json" rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" - name: Prepare threat detection files if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + rm -f /tmp/gh-aw/agent_usage.json cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then + echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context." + fi cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true for f in /tmp/gh-aw/aw-*.patch; do [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true @@ -1241,11 +1447,11 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1254,23 +1460,54 @@ jobs: timeout-minutes: 20 run: | set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + mkdir -p "$HOME/.copilot" + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" + export XDG_CONFIG_HOME="$HOME" touch /tmp/gh-aw/agent-step-summary.md GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.41/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true},"container":{"imageTag":"0.25.41"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + _GH_AW_CHROOT_JSON=$(jq -c --arg src /tmp/gh-aw --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home /tmp/gh-aw/home '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; } + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "/tmp/gh-aw/awf-config.json" + fi + GH_AW_TOOL_CACHE_MOUNT="" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" + if [ -d "$GH_AW_TOOL_CACHE" ]; then + if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then + GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" + fi + fi + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_API_KEY: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }} + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ github.token }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }} + GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.72.1 + GH_AW_TIMEOUT_MINUTES: 20 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1283,10 +1520,25 @@ jobs: GIT_AUTHOR_NAME: github-actions[bot] GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] - XDG_CONFIG_HOME: /home/runner + RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} + - name: Parse threat detection token usage for step summary + id: parse_detection_token_usage + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); - name: Upload threat detection log if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: detection path: /tmp/gh-aw/threat-detection/detection.log @@ -1298,6 +1550,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }} GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" with: script: | @@ -1308,10 +1561,11 @@ jobs: await main(); } catch (loadErr) { const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure'; const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); core.error(msg); core.setOutput('reason', 'parse_error'); - if (continueOnError) { + if (continueOnError && !detectionExecutionFailed) { core.warning('\u26A0\uFE0F ' + msg); core.setOutput('conclusion', 'warning'); core.setOutput('success', 'false'); @@ -1325,21 +1579,27 @@ jobs: pre_activation: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id runs-on: ubuntu-slim + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} matched_command: '' + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} env: GH_AW_SETUP_WORKFLOW_NAME: "Expert Code Reviewer 🧠" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/pr-expert-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Check team membership for workflow id: check_membership uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1363,18 +1623,24 @@ jobs: permissions: contents: read pull-requests: write - timeout-minutes: 15 + timeout-minutes: 45 env: + GH_AW_AGENT_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/pr-expert-reviewer" GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.40" + GH_AW_ENGINE_VERSION: "1.0.65" + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🧠 *Reviewed by [{workflow_name}]({run_url})*\",\"runStarted\":\"🔎 [{workflow_name}]({run_url}) is analyzing this PR for correctness, performance, and safety issues...\",\"runSuccess\":\"🧠 Analysis complete. [{workflow_name}]({run_url}) has finished the expert review. ✅\",\"runFailure\":\"⚠️ [{workflow_name}]({run_url}) {status}. Expert review could not be completed.\"}" + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "pr-expert-reviewer" GH_AW_WORKFLOW_NAME: "Expert Code Reviewer 🧠" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/pr-expert-reviewer.md" outputs: code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} @@ -1385,19 +1651,22 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Expert Code Reviewer 🧠" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/pr-expert-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1411,7 +1680,7 @@ jobs: - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash - run: | + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. GH_HOST="${GITHUB_SERVER_URL#https://}" @@ -1422,6 +1691,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} @@ -1435,7 +1705,7 @@ jobs: await main(); - name: Upload Safe Outputs Items if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: safe-outputs-items path: | @@ -1448,28 +1718,30 @@ jobs: - activation - agent - detection - if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: {} env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: prexpertreviewer steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "Expert Code Reviewer 🧠" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/pr-expert-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download cache-memory artifact (default) id: download_cache_default - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 continue-on-error: true with: name: cache-memory diff --git a/.github/workflows/pr-expert-reviewer.md b/.github/workflows/pr-expert-reviewer.md index eaea16022e..febd14cb3f 100644 --- a/.github/workflows/pr-expert-reviewer.md +++ b/.github/workflows/pr-expert-reviewer.md @@ -13,11 +13,11 @@ permissions: contents: read pull-requests: read actions: read + copilot-requests: write tools: cache-memory: true github: - lockdown: true toolsets: [pull_requests, repos] min-integrity: none diff --git a/.github/workflows/pr-iteration.lock.yml b/.github/workflows/pr-iteration.lock.yml index 281416d4c2..fe35cc5d59 100644 --- a/.github/workflows/pr-iteration.lock.yml +++ b/.github/workflows/pr-iteration.lock.yml @@ -1,5 +1,7 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"2933553e195e39bd42b6095ed3dd4d0c736fef4d04dc2243d355654f8ee2a9ea","compiler_version":"v0.72.1","strict":true,"agent_id":"copilot"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"bc56a0cad2f450c562810785ef38649c04db812a","version":"v0.72.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.41"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.41"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"131f7005c3f4654ddecad7c2f16505bf11573e72630fd96ce4d847c2b6431b70","body_hash":"863bfbb7aacd571660715d84914dfc8ac7dcb7b8cc7d197aec8a4c85603d8186","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} +# gh-aw-manifest: {"version":1,"secrets":["APP_PRIVATE_KEY","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/create-github-app-token","sha":"bcd2ba49218906704ab6c1aa796996da409d3eb1","version":"v3.2.0"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} +# This file was automatically generated by gh-aw (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -14,7 +16,6 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.72.1). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -29,7 +30,7 @@ # - shared/repo-build-setup.md # # Secrets used: -# - COPILOT_GITHUB_TOKEN +# - APP_PRIVATE_KEY # - GH_AW_CI_TRIGGER_TOKEN # - GH_AW_GITHUB_MCP_SERVER_TOKEN # - GH_AW_GITHUB_TOKEN @@ -38,23 +39,25 @@ # Custom actions used: # - actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 # - actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 -# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 -# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 +# - actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 +# - actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 -# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 +# - actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 +# - github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.25.41 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41 -# - ghcr.io/github/gh-aw-firewall/squid:0.25.41 -# - ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c -# - ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 -# - node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f +# - ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d +# - ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d +# - ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be +# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b +# - ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 name: "PR Iteration Agent 🔧" -"on": +on: issue_comment: types: - created @@ -68,14 +71,14 @@ name: "PR Iteration Agent 🔧" inputs: aw_context: default: "" - description: Agent caller context (used internally by Agentic Workflows). + description: "Agent caller context (used internally by Agentic Workflows)." required: false type: string permissions: {} concurrency: - group: "gh-aw-${{ github.workflow }}-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }}" + group: "gh-aw-${{ github.workflow }}-${{ contains(github.actor, '[bot]') && github.run_id || github.event.issue.number || github.event.pull_request.number || github.run_id }}" cancel-in-progress: true run-name: "PR Iteration Agent 🔧" @@ -88,14 +91,21 @@ jobs: permissions: actions: read contents: read + env: + GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: "" comment_repo: "" + daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }} + daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }} + daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }} engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} - secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} text: ${{ steps.sanitized.outputs.text }} @@ -103,37 +113,38 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }} + safe-output-artifact-client: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} env: GH_AW_SETUP_WORKFLOW_NAME: "PR Iteration Agent 🔧" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/pr-iteration.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.40" - GH_AW_INFO_AGENT_VERSION: "1.0.40" - GH_AW_INFO_CLI_VERSION: "v0.72.1" + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AGENT_VERSION: "1.0.65" + GH_AW_INFO_CLI_VERSION: "v0.81.6" GH_AW_INFO_WORKFLOW_NAME: "PR Iteration Agent 🔧" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","dotnet"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.41" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" - GITHUB_MCP_LOCKDOWN_EXPLICIT: "true" - GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} - GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | @@ -141,18 +152,58 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); - - name: Validate COPILOT_GITHUB_TOKEN secret - id: validate-secret - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-priteration-${{ github.run_id }} + restore-keys: agentic-workflow-usage-priteration- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Restore daily AIC usage cache (artifact fallback) + id: restore-daily-aic-cache-fallback + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_RESTORE_DAILY_AIC_CACHE_HIT: ${{ steps.restore-daily-aic-cache.outputs.cache-hit }} + GH_AW_RESTORE_DAILY_AIC_CACHE_MATCHED_KEY: ${{ steps.restore-daily-aic-cache.outputs.cache-matched-key }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs'); + await main(); + - name: Check daily workflow token guardrail + id: daily-effective-workflow-guardrail + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_WORKFLOW_NAME: "PR Iteration Agent 🔧" + GH_AW_WORKFLOW_ID: "pr-iteration" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} + GH_AW_HAS_SLASH_COMMAND: "false" + GH_AW_HAS_LABEL_COMMAND: "false" + GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs'); + await main(); - name: Checkout .github and .agents folders - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false sparse-checkout: | .github .agents + .antigravity .claude .codex .crush @@ -163,8 +214,8 @@ jobs: fetch-depth: 1 - name: Save agent config folders for base branch restoration env: - GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" - GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" # poutine:ignore untrusted_checkout_exec run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" - name: Check workflow lock file @@ -182,7 +233,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.72.1" + GH_AW_COMPILED_VERSION: "v0.81.6" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -200,15 +251,18 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/compute_text.cjs'); await main(); + - name: Log runtime features + if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh" - name: Create prompt with built-in context env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} @@ -217,53 +271,53 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_218306b2cf621906_EOF' + cat << 'GH_AW_PROMPT_90fa28c6f6dabdc5_EOF' - GH_AW_PROMPT_218306b2cf621906_EOF + GH_AW_PROMPT_90fa28c6f6dabdc5_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_218306b2cf621906_EOF' + cat << 'GH_AW_PROMPT_90fa28c6f6dabdc5_EOF' Tools: add_comment(max:3), reply_to_pull_request_review_comment(max:10), resolve_pull_request_review_thread(max:10), push_to_pull_request_branch(max:3), missing_tool, missing_data, noop - GH_AW_PROMPT_218306b2cf621906_EOF + GH_AW_PROMPT_90fa28c6f6dabdc5_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md" - cat << 'GH_AW_PROMPT_218306b2cf621906_EOF' + cat << 'GH_AW_PROMPT_90fa28c6f6dabdc5_EOF' - GH_AW_PROMPT_218306b2cf621906_EOF + GH_AW_PROMPT_90fa28c6f6dabdc5_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_218306b2cf621906_EOF' + cat << 'GH_AW_PROMPT_90fa28c6f6dabdc5_EOF' The following GitHub context information is available for this workflow: - {{#if __GH_AW_GITHUB_ACTOR__ }} + {{#if github.actor}} - **actor**: __GH_AW_GITHUB_ACTOR__ {{/if}} - {{#if __GH_AW_GITHUB_REPOSITORY__ }} + {{#if github.repository}} - **repository**: __GH_AW_GITHUB_REPOSITORY__ {{/if}} - {{#if __GH_AW_GITHUB_WORKSPACE__ }} + {{#if github.workspace}} - **workspace**: __GH_AW_GITHUB_WORKSPACE__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} - - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}} + - **issue-number**: #__GH_AW_EXPR_802A9F6A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} - - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}} + - **discussion-number**: #__GH_AW_EXPR_1A3A194A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} - - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}} + - **pull-request-number**: #__GH_AW_EXPR_463A214A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} - - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{#if github.event.comment.id || github.aw.context.comment_id}} + - **comment-id**: __GH_AW_EXPR_FF1D34CE__ {{/if}} - {{#if __GH_AW_GITHUB_RUN_ID__ }} + {{#if github.run_id}} - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ {{/if}} - GH_AW_PROMPT_218306b2cf621906_EOF + GH_AW_PROMPT_90fa28c6f6dabdc5_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" @@ -271,11 +325,11 @@ jobs: if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_push_to_pr_branch_guidance.md" fi - cat << 'GH_AW_PROMPT_218306b2cf621906_EOF' + cat << 'GH_AW_PROMPT_90fa28c6f6dabdc5_EOF' {{#runtime-import .github/workflows/shared/repo-build-setup.md}} {{#runtime-import .github/workflows/pr-iteration.md}} - GH_AW_PROMPT_218306b2cf621906_EOF + GH_AW_PROMPT_90fa28c6f6dabdc5_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -296,11 +350,11 @@ jobs: GH_AW_ALLOWED_EXTENSIONS: '' GH_AW_CACHE_DESCRIPTION: '' GH_AW_CACHE_DIR: '/tmp/gh-aw/cache-memory/' + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} @@ -321,11 +375,11 @@ jobs: GH_AW_ALLOWED_EXTENSIONS: process.env.GH_AW_ALLOWED_EXTENSIONS, GH_AW_CACHE_DESCRIPTION: process.env.GH_AW_CACHE_DESCRIPTION, GH_AW_CACHE_DIR: process.env.GH_AW_CACHE_DIR, + GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A, + GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A, + GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A, + GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE, GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, - GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, @@ -346,26 +400,30 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: activation include-hidden-files: true path: | /tmp/gh-aw/aw_info.json + /tmp/gh-aw/models.json /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/aw-prompts/prompt-template.txt /tmp/gh-aw/aw-prompts/prompt-import-tree.json /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/base /tmp/gh-aw/.github/agents + /tmp/gh-aw/.github/skills if-no-files-found: ignore retention-days: 1 agent: needs: activation + if: needs.activation.outputs.daily_ai_credits_exceeded != 'true' runs-on: ubuntu-latest permissions: contents: read + copilot-requests: write issues: read pull-requests: read env: @@ -374,31 +432,43 @@ jobs: GH_AW_ASSETS_BRANCH: "" GH_AW_ASSETS_MAX_SIZE_KB: 0 GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: priteration outputs: - agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} + agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} + ai_credits_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.ai_credits_rate_limit_error || 'false' }} + aic: ${{ steps.parse-mcp-gateway.outputs.aic }} + ambient_context: ${{ steps.parse-mcp-gateway.outputs.ambient_context }} + cache_memory_restore_0_cache_hit: ${{ steps.restore_cache_memory_0.outputs.cache-hit || 'false' }} + cache_memory_restore_0_matched_key: ${{ steps.restore_cache_memory_0.outputs.cache-matched-key || '' }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} - inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} - mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} + inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} model: ${{ needs.activation.outputs.model }} - model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} + model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} output: ${{ steps.collect_output.outputs.output }} output_types: ${{ steps.collect_output.outputs.output_types }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} + unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "PR Iteration Agent 🔧" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/pr-iteration.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths run: | @@ -408,7 +478,7 @@ jobs: echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false - name: Create gh-aw temp directory @@ -421,6 +491,7 @@ jobs: - name: Create cache-memory directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_cache_memory_dir.sh" - name: Restore cache-memory file share data + id: restore_cache_memory_0 uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: key: memory-none-9f0b69b3-${{ env.GH_AW_WORKFLOW_ID_SANITIZED }}-${{ github.run_id }} @@ -434,21 +505,14 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/setup_cache_memory_git.sh" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Checkout PR branch id: checkout-pr if: | - github.event.pull_request || github.event.issue.pull_request + github.event.pull_request || github.event.issue.pull_request || github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -460,11 +524,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Parse integrity filter lists id: parse-guard-vars env: @@ -473,33 +537,35 @@ jobs: GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: activation path: /tmp/gh-aw - name: Restore agent config folders from base branch if: steps.checkout-pr.outcome == 'success' env: - GH_AW_AGENT_FOLDERS: ".agents .claude .codex .crush .gemini .github .opencode .pi" - GH_AW_AGENT_FILES: ".crush.json AGENTS.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" - name: Restore inline sub-agents from activation artifact env: GH_AW_SUB_AGENT_DIR: ".github/agents" GH_AW_SUB_AGENT_EXT: ".agent.md" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" + - name: Restore inline skills from activation artifact + env: + GH_AW_SKILL_DIR: ".github/skills" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41 ghcr.io/github/gh-aw-firewall/squid:0.25.41 ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959 node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 - name: Generate Safe Outputs Config - env: - GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << GH_AW_SAFE_OUTPUTS_CONFIG_4aa915f7b16f3d78_EOF - {"add_comment":{"hide_older_comments":true,"max":3,"target":"*"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"push_to_pull_request_branch":{"github-token":"${GH_AW_GITHUB_TOKEN}","if_no_changes":"warn","max":3,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"target":"*","title_prefix":"[fix] "},"reply_to_pull_request_review_comment":{"max":10,"target":"*"},"report_incomplete":{},"resolve_pull_request_review_thread":{"max":10}} - GH_AW_SAFE_OUTPUTS_CONFIG_4aa915f7b16f3d78_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_8206cf37f2c34cb2_EOF' + {"add_comment":{"hide_older_comments":true,"max":3,"target":"*"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"push_to_pull_request_branch":{"if_no_changes":"warn","max":3,"max_patch_size":4096,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"target":"*","title_prefix":"[fix] "},"reply_to_pull_request_review_comment":{"max":10,"target":"*"},"report_incomplete":{},"resolve_pull_request_review_thread":{"max":10}} + GH_AW_SAFE_OUTPUTS_CONFIG_8206cf37f2c34cb2_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -598,7 +664,6 @@ jobs: "defaultMax": 1, "fields": { "branch": { - "required": true, "type": "string", "sanitize": true, "maxLength": 256 @@ -669,53 +734,15 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); await main(); - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - - name: Start MCP Gateway id: start-mcp-gateway env: + GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }} GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -eo pipefail mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" @@ -735,21 +762,25 @@ jobs: export GH_AW_ENGINE="copilot" MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') - DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.6' + case "${DOCKER_HOST:-}" in + unix://* ) DOCKER_SOCK_PATH="${DOCKER_HOST#unix://}" ;; + /* ) DOCKER_SOCK_PATH="$DOCKER_HOST" ;; + * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; + esac + DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --name awmg-mcpg --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.3.30' - mkdir -p /home/runner/.copilot + mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_78a0331e0d9abb8a_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_d15caff2d70f2577_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.0.3", + "container": "ghcr.io/github/github-mcp-server:v1.4.0", "env": { - "GITHUB_HOST": "\${GITHUB_SERVER_URL}", - "GITHUB_LOCKDOWN_MODE": "1", - "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_HOST": "${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", "GITHUB_TOOLSETS": "pull_requests,repos,issues" }, @@ -764,10 +795,27 @@ jobs: } }, "safeoutputs": { - "type": "http", - "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + "type": "stdio", + "container": "ghcr.io/github/gh-aw-node", + "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"], + "args": ["-w", "\${GITHUB_WORKSPACE}"], + "entrypoint": "sh", + "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], + "env": { + "DEBUG": "*", + "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", + "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", + "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", + "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", + "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", + "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", + "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", + "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", + "GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}", + "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", + "GITHUB_TOKEN": "\${GITHUB_TOKEN}", + "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", + "RUNNER_TEMP": "\${RUNNER_TEMP}" }, "guard-policies": { "write-sink": { @@ -785,7 +833,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_78a0331e0d9abb8a_EOF + GH_AW_MCP_CONFIG_d15caff2d70f2577_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -813,25 +861,54 @@ jobs: timeout-minutes: 30 run: | set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + mkdir -p "$HOME/.copilot" + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" + export XDG_CONFIG_HOME="$HOME" + export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json" touch /tmp/gh-aw/agent-step-summary.md GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.41/awf-config.schema.json","network":{"allowDomains":["*.vsblob.vsassets.io","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.nuget.org","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","azuresearch-usnc.nuget.org","azuresearch-ussc.nuget.org","builds.dotnet.microsoft.com","ci.dot.net","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","dc.services.visualstudio.com","dist.nuget.org","dot.net","dotnet.microsoft.com","dotnetcli.blob.core.windows.net","github.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","nuget.org","nuget.pkg.github.com","nugetregistryv2prod.blob.core.windows.net","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","oneocsp.microsoft.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","pkgs.dev.azure.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com","www.microsoft.com"]},"apiProxy":{"enabled":true,"models":{"auto":["large"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash"],"opus":["copilot/*opus*","anthropic/*opus*"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"]}},"container":{"imageTag":"0.25.41"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir /tmp/gh-aw/cache-memory/ --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.vsblob.vsassets.io\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.nuget.org\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"azuresearch-usnc.nuget.org\",\"azuresearch-ussc.nuget.org\",\"builds.dotnet.microsoft.com\",\"ci.dot.net\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"dc.services.visualstudio.com\",\"dist.nuget.org\",\"dot.net\",\"dotnet.microsoft.com\",\"dotnetcli.blob.core.windows.net\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"nuget.org\",\"nuget.pkg.github.com\",\"nugetregistryv2prod.blob.core.windows.net\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"oneocsp.microsoft.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"pkgs.dev.azure.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\",\"www.microsoft.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + GH_AW_CHROOT_BINARIES_SOURCE_PATH=/tmp/gh-aw GH_AW_CHROOT_IDENTITY_HOME=/tmp/gh-aw/home node "${RUNNER_TEMP}/gh-aw/actions/patch_awf_chroot_config.cjs" + fi + GH_AW_TOOL_CACHE_MOUNT="" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" + if [ -d "$GH_AW_TOOL_CACHE" ]; then + if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then + GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" + fi + fi + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir /tmp/gh-aw/cache-memory/ --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_API_KEY: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ github.token }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} + GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.72.1 + GH_AW_TIMEOUT_MINUTES: 30 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -845,25 +922,20 @@ jobs: GIT_AUTHOR_NAME: github-actions[bot] GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] - XDG_CONFIG_HOME: /home/runner - - name: Detect Copilot errors - id: detect-copilot-errors + RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} + - name: Detect agent errors if: always() + id: detect-agent-errors continue-on-error: true - run: node "${RUNNER_TEMP}/gh-aw/actions/detect_copilot_errors.cjs" + run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Copy Copilot session state files to logs if: always() continue-on-error: true @@ -887,8 +959,7 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); await main(); env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -984,16 +1055,23 @@ jobs: env: GH_AW_CACHE_DIR: /tmp/gh-aw/cache-memory run: bash "${RUNNER_TEMP}/gh-aw/actions/commit_cache_memory_git.sh" + - name: Check cache-memory git integrity + if: always() + continue-on-error: true + env: + GH_AW_CACHE_DIR: /tmp/gh-aw/cache-memory + run: bash "${RUNNER_TEMP}/gh-aw/actions/check_cache_memory_git_integrity.sh" - name: Upload cache-memory data as artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 if: always() with: name: cache-memory + include-hidden-files: true path: /tmp/gh-aw/cache-memory - name: Upload agent artifacts if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: agent path: | @@ -1027,16 +1105,18 @@ jobs: - update_cache_memory if: > always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || - needs.activation.outputs.stale_lock_file_failed == 'true') + needs.activation.outputs.stale_lock_file_failed == 'true' || needs.activation.outputs.daily_ai_credits_exceeded == 'true') runs-on: ubuntu-slim permissions: contents: write - discussions: write issues: write pull-requests: write concurrency: group: "gh-aw-conclusion-pr-iteration" cancel-in-progress: false + queue: max + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} @@ -1045,19 +1125,36 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "PR Iteration Agent 🔧" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/pr-iteration.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Generate GitHub App token + id: safe-outputs-app-token + if: ${{ vars.APP_ID != '' && secrets.APP_PRIVATE_KEY != '' }} + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + repositories: ${{ github.event.repository.name }} + github-api-url: ${{ github.api_url }} + permission-administration: read + permission-contents: write + permission-issues: write + permission-pull-requests: write - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1068,6 +1165,88 @@ jobs: mkdir -p /tmp/gh-aw/ find "/tmp/gh-aw/" -type f -print echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Collect usage artifact files + if: always() + continue-on-error: true + run: | + mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection + echo "Usage artifact source file status:" + for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do + [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file" + done + [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true + [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true + [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true + [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true + [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true + [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl + [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl + mkdir -p /tmp/gh-aw/usage/activity + node ${{ runner.temp }}/gh-aw/actions/generate_usage_activity_summary.cjs + find /tmp/gh-aw/usage -type f -print | sort + - name: Upload usage artifact + if: always() + continue-on-error: true + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: usage + path: | + /tmp/gh-aw/usage/aw_info.json + /tmp/gh-aw/usage/aw-info.jsonl + /tmp/gh-aw/usage/agent_usage.json + /tmp/gh-aw/usage/agent_usage.jsonl + /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/github_rate_limits.jsonl + /tmp/gh-aw/usage/agent/token_usage.jsonl + /tmp/gh-aw/usage/detection/token_usage.jsonl + /tmp/gh-aw/usage/activity/summary.json + if-no-files-found: ignore + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache-conclusion + if: always() + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-priteration-${{ github.run_id }} + restore-keys: agentic-workflow-usage-priteration- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Write daily AIC usage cache entry + id: write-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context); + const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs'); + await main(); + - name: Save daily AIC usage cache + id: save-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-priteration-${{ github.run_id }} + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Upload daily AIC usage cache artifact + id: upload-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: aic-usage-cache + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + if-no-files-found: ignore + retention-days: 7 - name: Process no-op messages id: noop uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1075,11 +1254,16 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" GH_AW_WORKFLOW_NAME: "PR Iteration Agent 🔧" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/pr-iteration.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_NOOP_REPORT_AS_ISSUE: "false" + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} + GH_AW_WORKFLOW_ID: "pr-iteration" with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ steps.safe-outputs-app-token.outputs.token || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -1091,11 +1275,12 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "PR Iteration Agent 🔧" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/pr-iteration.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ steps.safe-outputs-app-token.outputs.token || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -1108,8 +1293,9 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" GH_AW_WORKFLOW_NAME: "PR Iteration Agent 🔧" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/pr-iteration.md" with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ steps.safe-outputs-app-token.outputs.token || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -1122,8 +1308,9 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" GH_AW_WORKFLOW_NAME: "PR Iteration Agent 🔧" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/pr-iteration.md" with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ steps.safe-outputs-app-token.outputs.token || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -1136,13 +1323,19 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "PR Iteration Agent 🔧" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/pr-iteration.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_WORKFLOW_ID: "pr-iteration" GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" - GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} + GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} + GH_AW_UNKNOWN_MODEL_AI_CREDITS: ${{ needs.agent.outputs.unknown_model_ai_credits || 'false' }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} @@ -1150,8 +1343,13 @@ jobs: GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }} GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }} + GH_AW_SAFE_OUTPUTS_APP_TOKEN_MINTING_FAILED: ${{ needs.safe_outputs.outputs.app_token_minting_failed }} + GH_AW_CONCLUSION_APP_TOKEN_MINTING_FAILED: ${{ steps.safe-outputs-app-token.outcome == 'failure' }} GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_DAILY_AI_CREDITS_EXCEEDED: ${{ needs.activation.outputs.daily_ai_credits_exceeded }} + GH_AW_DAILY_AI_CREDITS_TOTAL_EFFECTIVE_TOKENS: ${{ needs.activation.outputs.daily_ai_credits_total_effective_tokens }} + GH_AW_DAILY_AI_CREDITS_THRESHOLD: ${{ needs.activation.outputs.daily_ai_credits_threshold }} GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔧 *Iterated by [{workflow_name}]({run_url})*\"}" GH_AW_GROUP_REPORTS: "false" GH_AW_FAILURE_REPORT_AS_ISSUE: "true" @@ -1159,8 +1357,10 @@ jobs: GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" GH_AW_TIMEOUT_MINUTES: "30" GH_AW_CACHE_MEMORY_ENABLED: "true" + GH_AW_CACHE_MEMORY_RESTORE_0_MATCHED_KEY: ${{ needs.agent.outputs.cache_memory_restore_0_matched_key || '' }} + GH_AW_CACHE_MEMORY_RESTORE_0_CACHE_HIT: ${{ needs.agent.outputs.cache_memory_restore_0_cache_hit || 'false' }} with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ steps.safe-outputs-app-token.outputs.token || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -1171,31 +1371,37 @@ jobs: needs: - activation - agent - if: > - always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + if: always() && needs.agent.result != 'skipped' runs-on: ubuntu-latest permissions: contents: read + copilot-requests: write + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: + aic: ${{ steps.parse_detection_token_usage.outputs.aic }} detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} detection_reason: ${{ steps.detection_conclusion.outputs.reason }} detection_success: ${{ steps.detection_conclusion.outputs.success }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "PR Iteration Agent 🔧" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/pr-iteration.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1208,7 +1414,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Checkout repository for patch context if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false # --- Threat Detection --- @@ -1217,7 +1423,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.41 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.41 ghcr.io/github/gh-aw-firewall/squid:0.25.41 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d - name: Check if detection needed id: detection_guard if: always() @@ -1236,13 +1442,17 @@ jobs: if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" - rm -f /home/runner/.copilot/mcp-config.json + rm -f "$HOME/.copilot/mcp-config.json" rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" - name: Prepare threat detection files if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + rm -f /tmp/gh-aw/agent_usage.json cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then + echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context." + fi cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true for f in /tmp/gh-aw/aw-*.patch; do [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true @@ -1276,11 +1486,11 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.40 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.41 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1289,23 +1499,54 @@ jobs: timeout-minutes: 20 run: | set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + mkdir -p "$HOME/.copilot" + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" + export XDG_CONFIG_HOME="$HOME" touch /tmp/gh-aw/agent-step-summary.md GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.41/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true},"container":{"imageTag":"0.25.41"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" && cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || echo node)"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + _GH_AW_CHROOT_JSON=$(jq -c --arg src /tmp/gh-aw --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home /tmp/gh-aw/home '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; } + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "/tmp/gh-aw/awf-config.json" + fi + GH_AW_TOOL_CACHE_MOUNT="" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" + if [ -d "$GH_AW_TOOL_CACHE" ]; then + if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then + GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" + fi + fi + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_API_KEY: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }} + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ github.token }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }} + GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.72.1 + GH_AW_TIMEOUT_MINUTES: 20 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1318,10 +1559,25 @@ jobs: GIT_AUTHOR_NAME: github-actions[bot] GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] - XDG_CONFIG_HOME: /home/runner + RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} + - name: Parse threat detection token usage for step summary + id: parse_detection_token_usage + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); - name: Upload threat detection log if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: detection path: /tmp/gh-aw/threat-detection/detection.log @@ -1333,6 +1589,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }} GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" with: script: | @@ -1343,10 +1600,11 @@ jobs: await main(); } catch (loadErr) { const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure'; const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); core.error(msg); core.setOutput('reason', 'parse_error'); - if (continueOnError) { + if (continueOnError && !detectionExecutionFailed) { core.warning('\u26A0\uFE0F ' + msg); core.setOutput('conclusion', 'warning'); core.setOutput('success', 'false'); @@ -1361,21 +1619,27 @@ jobs: if: > github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' || contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association) runs-on: ubuntu-slim + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} matched_command: '' + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} env: GH_AW_SETUP_WORKFLOW_NAME: "PR Iteration Agent 🔧" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/pr-iteration.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Check team membership for workflow id: check_membership uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1398,22 +1662,28 @@ jobs: runs-on: ubuntu-slim permissions: contents: write - discussions: write issues: write pull-requests: write - timeout-minutes: 15 + timeout-minutes: 45 env: + GH_AW_AGENT_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/pr-iteration" GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.40" + GH_AW_ENGINE_VERSION: "1.0.65" + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🔧 *Iterated by [{workflow_name}]({run_url})*\"}" + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "pr-iteration" GH_AW_WORKFLOW_NAME: "PR Iteration Agent 🔧" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/pr-iteration.md" outputs: + app_token_minting_failed: ${{ steps.safe-outputs-app-token.outcome == 'failure' }} code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} comment_id: ${{ steps.process_safe_outputs.outputs.comment_id }} @@ -1427,19 +1697,22 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "PR Iteration Agent 🔧" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/pr-iteration.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1452,59 +1725,41 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Download patch artifact continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ - - name: Extract base branch from agent output - id: extract-base-branch - if: steps.download-agent-output.outcome == 'success' - shell: bash - run: | - if [ -f "/tmp/gh-aw/agent_output.json" ]; then - GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - BASE_BRANCH=$("$GH_AW_NODE" -e " - try { - const data = JSON.parse(require('fs').readFileSync('/tmp/gh-aw/agent_output.json', 'utf8')); - const item = (data.items || []).find(i => - (i.type === 'create_pull_request' || i.type === 'push_to_pull_request_branch') && - i.base_branch - ); - if (item) process.stdout.write(item.base_branch); - } catch(e) {} - " 2>/dev/null || true) - # Validate: only allow safe git branch name characters - if [[ "$BASE_BRANCH" =~ ^[a-zA-Z0-9/_.-]+$ ]] && [ ${#BASE_BRANCH} -le 255 ]; then - printf 'base-branch=%s\n' "$BASE_BRANCH" >> "$GITHUB_OUTPUT" - echo "Extracted base branch from safe output: $BASE_BRANCH" - fi - fi + - name: Generate GitHub App token + id: safe-outputs-app-token + if: ${{ vars.APP_ID != '' && secrets.APP_PRIVATE_KEY != '' }} + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + with: + client-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + owner: ${{ github.repository_owner }} + repositories: ${{ github.event.repository.name }} + github-api-url: ${{ github.api_url }} + permission-administration: read + permission-contents: write + permission-issues: write + permission-pull-requests: write - name: Checkout repository if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch') - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: - ref: ${{ steps.extract-base-branch.outputs.base-branch || github.base_ref || github.event.pull_request.base.ref || github.ref_name || github.event.repository.default_branch }} - token: ${{ secrets.GH_AW_GITHUB_TOKEN }} - persist-credentials: false - fetch-depth: 1 + persist-credentials: true + token: ${{ steps.safe-outputs-app-token.outputs.token || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - name: Configure Git credentials if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch') env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} - GIT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GIT_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GIT_TOKEN: ${{ steps.safe-outputs-app-token.outputs.token || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash - run: | + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. GH_HOST="${GITHUB_SERVER_URL#https://}" @@ -1515,14 +1770,15 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} GH_AW_ALLOWED_DOMAINS: "*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":3,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_to_pull_request_branch\":{\"github-token\":\"${{ secrets.GH_AW_GITHUB_TOKEN }}\",\"if_no_changes\":\"warn\",\"max\":3,\"max_patch_size\":1024,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"target\":\"*\",\"title_prefix\":\"[fix] \"},\"reply_to_pull_request_review_comment\":{\"max\":10,\"target\":\"*\"},\"report_incomplete\":{},\"resolve_pull_request_review_thread\":{\"max\":10}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":3,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max\":3,\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"target\":\"*\",\"title_prefix\":\"[fix] \"},\"reply_to_pull_request_review_comment\":{\"max\":10,\"target\":\"*\"},\"report_incomplete\":{},\"resolve_pull_request_review_thread\":{\"max\":10}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ steps.safe-outputs-app-token.outputs.token || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ steps.safe-outputs-app-token.outputs.token || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -1530,7 +1786,7 @@ jobs: await main(); - name: Upload Safe Outputs Items if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: safe-outputs-items path: | @@ -1543,28 +1799,30 @@ jobs: - activation - agent - detection - if: > - always() && (needs.detection.result == 'success' || needs.detection.result == 'skipped') && - needs.agent.result == 'success' + if: always() && needs.detection.result == 'success' && needs.agent.result == 'success' runs-on: ubuntu-slim permissions: {} env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: priteration steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@bc56a0cad2f450c562810785ef38649c04db812a # v0.72.1 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} env: GH_AW_SETUP_WORKFLOW_NAME: "PR Iteration Agent 🔧" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/pr-iteration.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.40" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download cache-memory artifact (default) id: download_cache_default - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 continue-on-error: true with: name: cache-memory diff --git a/.github/workflows/pr-iteration.md b/.github/workflows/pr-iteration.md index fba9f54406..681ed0f39b 100644 --- a/.github/workflows/pr-iteration.md +++ b/.github/workflows/pr-iteration.md @@ -16,6 +16,7 @@ permissions: contents: read pull-requests: read issues: read + copilot-requests: write network: allowed: @@ -25,13 +26,20 @@ network: tools: cache-memory: true github: - lockdown: true toolsets: [pull_requests, repos, issues] min-integrity: none bash: true edit: safe-outputs: + # Prefer an org-owned GitHub App: it mints a short-lived, auto-revoked token + # scoped to this job and (unlike GITHUB_TOKEN) triggers CI on pushed commits. + # ignore-if-missing lets the workflow fall back to GITHUB_TOKEN when the App + # secrets are absent, so it still runs without org-admin setup and on forks. + github-app: + client-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + ignore-if-missing: true noop: report-as-issue: false add-comment: @@ -42,7 +50,6 @@ safe-outputs: target: "*" title-prefix: "[fix] " max: 3 - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN }} reply-to-pull-request-review-comment: max: 10 target: "*" diff --git a/.github/workflows/repository-quality-improver.lock.yml b/.github/workflows/repository-quality-improver.lock.yml index 6dcfd2b330..f77db4acbd 100644 --- a/.github/workflows/repository-quality-improver.lock.yml +++ b/.github/workflows/repository-quality-improver.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"bf666dc0eb5029e87aebd2e9851b6bc2f0c612a722321647e97bf6ad746f49f7","body_hash":"729f63a37bcfa6c5ce800b2c0d63526ae78981d7db7efd7042c25194b4722dce","compiler_version":"v0.79.8","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.60"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"c0338fef4749d08c21f8f975fb0e37efa17dda47","version":"v0.79.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]} -# This file was automatically generated by gh-aw (v0.79.8). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"49c253db8ac79a287bf2fa4f8ea6f8c25bd083a17b0a03f5eb6a9af5146213d6","body_hash":"729f63a37bcfa6c5ce800b2c0d63526ae78981d7db7efd7042c25194b4722dce","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} +# gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} +# This file was automatically generated by gh-aw (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -26,7 +26,6 @@ # Daily analysis of repository quality focusing on a different software development lifecycle area each run # # Secrets used: -# - COPILOT_GITHUB_TOKEN # - GH_AW_GITHUB_MCP_SERVER_TOKEN # - GH_AW_GITHUB_TOKEN # - GITHUB_TOKEN @@ -34,20 +33,22 @@ # Custom actions used: # - actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 # - actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 -# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 -# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 +# - actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 -# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 +# - actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 +# - github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 -# - ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa -# - ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c +# - ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d +# - ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d +# - ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be +# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b +# - ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 name: "Repository Quality Improver" on: @@ -77,6 +78,7 @@ jobs: contents: read env: GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: comment_id: "" comment_repo: "" @@ -86,7 +88,6 @@ jobs: engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} - secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} @@ -94,7 +95,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -102,8 +103,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Repository Quality Improver" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/repository-quality-improver.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info @@ -111,16 +112,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AGENT_VERSION: "1.0.60" - GH_AW_INFO_CLI_VERSION: "v0.79.8" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AGENT_VERSION: "1.0.65" + GH_AW_INFO_CLI_VERSION: "v0.81.6" GH_AW_INFO_WORKFLOW_NAME: "Repository Quality Improver" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -131,6 +132,30 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-repositoryqualityimprover-${{ github.run_id }} + restore-keys: agentic-workflow-usage-repositoryqualityimprover- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Restore daily AIC usage cache (artifact fallback) + id: restore-daily-aic-cache-fallback + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_RESTORE_DAILY_AIC_CACHE_HIT: ${{ steps.restore-daily-aic-cache.outputs.cache-hit }} + GH_AW_RESTORE_DAILY_AIC_CACHE_MATCHED_KEY: ${{ steps.restore-daily-aic-cache.outputs.cache-matched-key }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs'); + await main(); - name: Check daily workflow token guardrail id: daily-effective-workflow-guardrail if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} @@ -140,6 +165,8 @@ jobs: GH_AW_WORKFLOW_ID: "repository-quality-improver" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} + GH_AW_HAS_SLASH_COMMAND: "false" + GH_AW_HAS_LABEL_COMMAND: "false" GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} with: @@ -149,13 +176,8 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs'); await main(); - - name: Validate COPILOT_GITHUB_TOKEN secret - id: validate-secret - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Checkout .github and .agents folders - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false sparse-checkout: | @@ -191,13 +213,16 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.79.8" + GH_AW_COMPILED_VERSION: "v0.81.6" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); await main(); + - name: Log runtime features + if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh" - name: Create prompt with built-in context env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt @@ -214,21 +239,21 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_9aebe02d43e3c07f_EOF' + cat << 'GH_AW_PROMPT_8deb23cc10663abc_EOF' - GH_AW_PROMPT_9aebe02d43e3c07f_EOF + GH_AW_PROMPT_8deb23cc10663abc_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/cache_memory_prompt_multi.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_9aebe02d43e3c07f_EOF' + cat << 'GH_AW_PROMPT_8deb23cc10663abc_EOF' Tools: create_issue, missing_tool, missing_data, noop - GH_AW_PROMPT_9aebe02d43e3c07f_EOF + GH_AW_PROMPT_8deb23cc10663abc_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_9aebe02d43e3c07f_EOF' + cat << 'GH_AW_PROMPT_8deb23cc10663abc_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -257,12 +282,12 @@ jobs: {{/if}} - GH_AW_PROMPT_9aebe02d43e3c07f_EOF + GH_AW_PROMPT_8deb23cc10663abc_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_9aebe02d43e3c07f_EOF' + cat << 'GH_AW_PROMPT_8deb23cc10663abc_EOF' {{#runtime-import .github/workflows/repository-quality-improver.md}} - GH_AW_PROMPT_9aebe02d43e3c07f_EOF + GH_AW_PROMPT_8deb23cc10663abc_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -329,7 +354,7 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: activation include-hidden-files: true @@ -353,6 +378,7 @@ jobs: permissions: actions: read contents: read + copilot-requests: write issues: read pull-requests: read concurrency: @@ -364,12 +390,15 @@ jobs: GH_AW_ASSETS_BRANCH: "" GH_AW_ASSETS_MAX_SIZE_KB: 0 GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: repositoryqualityimprover outputs: agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} ai_credits_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.ai_credits_rate_limit_error || 'false' }} aic: ${{ steps.parse-mcp-gateway.outputs.aic }} ambient_context: ${{ steps.parse-mcp-gateway.outputs.ambient_context }} + cache_memory_restore_0_cache_hit: ${{ steps.restore_cache_memory_0.outputs.cache-hit || 'false' }} + cache_memory_restore_0_matched_key: ${{ steps.restore_cache_memory_0.outputs.cache-matched-key || '' }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} @@ -386,7 +415,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -395,8 +424,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Repository Quality Improver" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/repository-quality-improver.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -407,7 +436,7 @@ jobs: echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: persist-credentials: false - name: Create gh-aw temp directory @@ -421,6 +450,7 @@ jobs: run: | mkdir -p /tmp/gh-aw/cache-memory-focus-areas - name: Restore cache-memory file share data (focus-areas) + id: restore_cache_memory_0 uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: key: memory-none-nopolicy-quality-focus-repository-quality-improver-${{ github.run_id }} @@ -434,17 +464,10 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/setup_cache_memory_git.sh" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Checkout PR branch id: checkout-pr if: | @@ -460,11 +483,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) @@ -476,7 +499,7 @@ jobs: const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: activation path: /tmp/gh-aw @@ -496,15 +519,15 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_a02c7399cba8e87d_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_8af7283ddd3ded52_EOF' {"create_issue":{"expires":48,"labels":["agentic-workflows"],"max":1},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_a02c7399cba8e87d_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_8af7283ddd3ded52_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -635,55 +658,17 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); await main(); - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - - name: Start MCP Gateway id: start-mcp-gateway env: + GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }} GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }} GITHUB_MCP_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }} GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -eo pipefail mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" @@ -709,19 +694,19 @@ jobs: * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; esac DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.25' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --name awmg-mcpg --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.3.30' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_c6fee03c27b97257_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_f014db59cae17bc3_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.1.2", + "container": "ghcr.io/github/github-mcp-server:v1.4.0", "env": { - "GITHUB_HOST": "\${GITHUB_SERVER_URL}", - "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_HOST": "${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}", "GITHUB_READ_ONLY": "1", "GITHUB_TOOLSETS": "context,repos,issues,pull_requests" }, @@ -733,10 +718,27 @@ jobs: } }, "safeoutputs": { - "type": "http", - "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + "type": "stdio", + "container": "ghcr.io/github/gh-aw-node", + "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"], + "args": ["-w", "\${GITHUB_WORKSPACE}"], + "entrypoint": "sh", + "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], + "env": { + "DEBUG": "*", + "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", + "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", + "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", + "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", + "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", + "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", + "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", + "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", + "GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}", + "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", + "GITHUB_TOKEN": "\${GITHUB_TOKEN}", + "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", + "RUNNER_TEMP": "\${RUNNER_TEMP}" }, "guard-policies": { "write-sink": { @@ -754,7 +756,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_c6fee03c27b97257_EOF + GH_AW_MCP_CONFIG_f014db59cae17bc3_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -793,38 +795,43 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + GH_AW_CHROOT_BINARIES_SOURCE_PATH=/tmp/gh-aw GH_AW_CHROOT_IDENTITY_HOME=/tmp/gh-aw/home node "${RUNNER_TEMP}/gh-aw/actions/patch_awf_chroot_config.cjs" fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir /tmp/gh-aw/cache-memory-focus-areas/ --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir /tmp/gh-aw/cache-memory-focus-areas/ --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -839,6 +846,8 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Detect agent errors if: always() id: detect-agent-errors @@ -846,17 +855,10 @@ jobs: run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Copy Copilot session state files to logs if: always() continue-on-error: true @@ -880,8 +882,7 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); await main(); env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -984,7 +985,7 @@ jobs: GH_AW_CACHE_DIR: /tmp/gh-aw/cache-memory-focus-areas run: bash "${RUNNER_TEMP}/gh-aw/actions/check_cache_memory_git_integrity.sh" - name: Upload cache-memory data as artifact (focus-areas) - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 if: always() with: name: cache-memory-focus-areas @@ -993,7 +994,7 @@ jobs: - name: Upload agent artifacts if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: agent path: | @@ -1034,6 +1035,8 @@ jobs: group: "gh-aw-conclusion-repository-quality-improver" cancel-in-progress: false queue: max + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} @@ -1042,7 +1045,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1051,13 +1054,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Repository Quality Improver" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/repository-quality-improver.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1074,34 +1077,82 @@ jobs: run: | mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection echo "Usage artifact source file status:" - for file in /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do + for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file" done + [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true + [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl + mkdir -p /tmp/gh-aw/usage/activity + node ${{ runner.temp }}/gh-aw/actions/generate_usage_activity_summary.cjs find /tmp/gh-aw/usage -type f -print | sort - name: Upload usage artifact if: always() continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: usage path: | + /tmp/gh-aw/usage/aw_info.json /tmp/gh-aw/usage/aw-info.jsonl + /tmp/gh-aw/usage/agent_usage.json /tmp/gh-aw/usage/agent_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/github_rate_limits.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl + /tmp/gh-aw/usage/activity/summary.json + if-no-files-found: ignore + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache-conclusion + if: always() + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-repositoryqualityimprover-${{ github.run_id }} + restore-keys: agentic-workflow-usage-repositoryqualityimprover- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Write daily AIC usage cache entry + id: write-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context); + const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs'); + await main(); + - name: Save daily AIC usage cache + id: save-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-repositoryqualityimprover-${{ github.run_id }} + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Upload daily AIC usage cache artifact + id: upload-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + with: + name: aic-usage-cache + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl if-no-files-found: ignore + retention-days: 7 - name: Process no-op messages id: noop uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1184,7 +1235,6 @@ jobs: GH_AW_WORKFLOW_ID: "repository-quality-improver" GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" - GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} @@ -1208,6 +1258,8 @@ jobs: GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" GH_AW_TIMEOUT_MINUTES: "20" GH_AW_CACHE_MEMORY_ENABLED: "true" + GH_AW_CACHE_MEMORY_RESTORE_0_MATCHED_KEY: ${{ needs.agent.outputs.cache_memory_restore_0_matched_key || '' }} + GH_AW_CACHE_MEMORY_RESTORE_0_CACHE_HIT: ${{ needs.agent.outputs.cache_memory_restore_0_cache_hit || 'false' }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1220,11 +1272,13 @@ jobs: needs: - activation - agent - if: > - always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + if: always() && needs.agent.result != 'skipped' runs-on: ubuntu-latest permissions: contents: read + copilot-requests: write + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: aic: ${{ steps.parse_detection_token_usage.outputs.aic }} detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} @@ -1233,7 +1287,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1242,13 +1296,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Repository Quality Improver" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/repository-quality-improver.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1261,7 +1315,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Checkout repository for patch context if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false # --- Threat Detection --- @@ -1270,7 +1324,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d - name: Check if detection needed id: detection_guard if: always() @@ -1333,11 +1387,11 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1356,37 +1410,44 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + _GH_AW_CHROOT_JSON=$(jq -c --arg src /tmp/gh-aw --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home /tmp/gh-aw/home '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; } + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "/tmp/gh-aw/awf-config.json" fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: v0.81.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1400,6 +1461,8 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Parse threat detection token usage for step summary id: parse_detection_token_usage if: always() @@ -1415,7 +1478,7 @@ jobs: await main(); - name: Upload threat detection log if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: detection path: /tmp/gh-aw/threat-detection/detection.log @@ -1474,7 +1537,8 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} - GH_AW_ENGINE_VERSION: "1.0.60" + GH_AW_ENGINE_VERSION: "1.0.65" + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "repository-quality-improver" GH_AW_WORKFLOW_NAME: "Repository Quality Improver" @@ -1491,7 +1555,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1500,13 +1564,13 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Repository Quality Improver" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/repository-quality-improver.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: agent path: /tmp/gh-aw/ @@ -1520,8 +1584,7 @@ jobs: - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash - # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. - run: | + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. GH_HOST="${GITHUB_SERVER_URL#https://}" @@ -1546,7 +1609,7 @@ jobs: await main(); - name: Upload Safe Outputs Items if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: safe-outputs-items path: | @@ -1563,11 +1626,12 @@ jobs: runs-on: ubuntu-slim permissions: {} env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} GH_AW_WORKFLOW_ID_SANITIZED: repositoryqualityimprover steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1576,12 +1640,12 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Repository Quality Improver" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/repository-quality-improver.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download cache-memory artifact (focus-areas) id: download_cache_focus_areas - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 continue-on-error: true with: name: cache-memory-focus-areas diff --git a/.github/workflows/repository-quality-improver.md b/.github/workflows/repository-quality-improver.md index 3b07c48447..e3a2988061 100644 --- a/.github/workflows/repository-quality-improver.md +++ b/.github/workflows/repository-quality-improver.md @@ -9,6 +9,7 @@ permissions: actions: read issues: read pull-requests: read + copilot-requests: write tools: bash: ["*"] diff --git a/AGENTS.md b/AGENTS.md index 8c8281197e..8322520fa2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -96,7 +96,8 @@ After packaging changes, regenerate `eng/expected-nupkg-file-counts.json` and `e ### Agentic Workflows (gh-aw) - Use `gh aw secrets set` to manage secrets, NOT `gh secret set`. Plain `gh secret set` creates the repo secret but gh-aw can't see it. -- Two secrets needed: `COPILOT_GITHUB_TOKEN` (Copilot API access) and `GH_AW_GITHUB_TOKEN` (repo interactions). +- **Auth is company-token first — no long-lived personal PATs.** Copilot inference uses the `copilot-requests: write` permission (billed to the org Copilot subscription), so `COPILOT_GITHUB_TOKEN` is no longer referenced by any compiled workflow. Write-backs use an org-owned GitHub App (`APP_ID` variable + `APP_PRIVATE_KEY` secret) with `ignore-if-missing: true`, falling back to `GITHUB_TOKEN` until an org admin provisions it. See [`.github/workflows/README.md`](.github/workflows/README.md) for the full secrets table and rationale (the Microsoft OSS enterprise now 403s fine-grained PATs older than 8 days). +- `lockdown:` has been removed repo-wide (deprecated upstream); workflows keep `min-integrity: none` and use the default `GITHUB_TOKEN` for MCP reads. - Workflow source files are `.md` in `.github/workflows/`. Compiled `.lock.yml` files are generated — don't hand-edit them. - To recompile after editing a workflow: `gh aw compile` from the repo root. - `.github/*` and `AGENTS.md` are excluded from CI path triggers — editing workflows won't trigger a full build. From 4ae2fb075798fdcaa9819abc3a4aefbb1cbf16c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Tue, 7 Jul 2026 14:54:34 +0200 Subject: [PATCH 25/87] Reframe efficiency-improver to chase big wins, not tiny ones (#16229) * Reframe efficiency-improver to chase big wins, not tiny ones The workflow was churning out lots of small perf PRs. Retarget it at high-impact, measured improvements only: weekly instead of daily, at most one PR per run (2 open max), and only HIGH-impact items are actionable (MEDIUM goes to the backlog, LOW is discarded). Add a High-Impact Bar and a 'Know the Workload' section so it optimises fixed per-invocation/per-process overhead - which dominates given the typical run is a single test and ~90% are under 1000 tests - instead of per-test loops tuned for test counts that essentially never occur. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Accept quadratic-or-worse growth at any scale Super-linear growth (O(n^2) or worse, including accidentally-quadratic patterns) is a scaling landmine, so it is welcome to fix even when the common run is a single test - it does not need a proven large N. Carve it out as an explicit exception to the discount-big-N rule. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../workflows/efficiency-improver.lock.yml | 51 +++++++------- .github/workflows/efficiency-improver.md | 70 +++++++++++++------ 2 files changed, 75 insertions(+), 46 deletions(-) diff --git a/.github/workflows/efficiency-improver.lock.yml b/.github/workflows/efficiency-improver.lock.yml index 8b247ac720..dd0d21a466 100644 --- a/.github/workflows/efficiency-improver.lock.yml +++ b/.github/workflows/efficiency-improver.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"78afcf7c1894d979c34194a405a64a8c8d334359d4ea3985ffaf67b9473db13f","body_hash":"71a906a87a507ddbfaf81b4cc9292b0da283515520eb1ad3f347e091d91af2e5","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"e73403fb5dceeaef7fd0e467ecc82f5da3b0b44b217b2133c0fb6d6cec45a63f","body_hash":"6a54aec1ad40b82f62febf375efc202bfceae22377239072557719f06479cc2a","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} # This file was automatically generated by gh-aw (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -23,9 +23,10 @@ # # For more information: https://github.github.com/gh-aw/introduction/overview/ # -# A green-software-focused repository assistant that runs regularly (daily by default) to identify and implement -# energy efficiency improvements. Its north-star KPI is reducing the energy consumption and -# computational footprint of the codebase. Always methodical, measurement-driven, and mindful of trade-offs. +# A green-software-focused repository assistant that runs weekly to identify and implement a small number of +# high-impact energy efficiency improvements. Its north-star KPI is a meaningful reduction in the energy +# consumption and computational footprint of the codebase — big wins on hot paths, not micro-optimisations. +# Always methodical, measurement-driven, and mindful of trade-offs. # # Secrets used: # - GH_AW_CI_TRIGGER_TOKEN @@ -59,10 +60,10 @@ on: # permissions: # Permissions applied to pre-activation job # pull-requests: read schedule: - - cron: "36 16 * * *" + - cron: "36 16 * * 6" # steps: # Steps injected into pre-activation job # - id: check - # run: "MAX_OPEN_PRS=8\nif [[ \"$GITHUB_EVENT_NAME\" != \"schedule\" ]]; then exit 0; fi\n# gh pr list exits with code 4 when --search returns no matches; treat that as 0 but\n# let other failures (auth, API, rate limit) propagate so we don't silently proceed.\nset +e\nCOUNT=$(gh pr list --repo \"$GITHUB_REPOSITORY\" --state open --search 'in:title \"[efficiency-improver]\"' --json number --jq 'length' 2>/dev/null)\nrc=$?\nset -e\ncase $rc in\n 0) ;;\n 4) COUNT=0 ;;\n *) echo \"gh pr list failed with exit code $rc\" >&2; exit $rc ;;\nesac\n[[ \"$COUNT\" -lt \"$MAX_OPEN_PRS\" ]]\n" + # run: "MAX_OPEN_PRS=2\nif [[ \"$GITHUB_EVENT_NAME\" != \"schedule\" ]]; then exit 0; fi\n# gh pr list exits with code 4 when --search returns no matches; treat that as 0 but\n# let other failures (auth, API, rate limit) propagate so we don't silently proceed.\nset +e\nCOUNT=$(gh pr list --repo \"$GITHUB_REPOSITORY\" --state open --search 'in:title \"[efficiency-improver]\"' --json number --jq 'length' 2>/dev/null)\nrc=$?\nset -e\ncase $rc in\n 0) ;;\n 4) COUNT=0 ;;\n *) echo \"gh pr list failed with exit code $rc\" >&2; exit $rc ;;\nesac\n[[ \"$COUNT\" -lt \"$MAX_OPEN_PRS\" ]]\n" workflow_dispatch: inputs: aw_context: @@ -266,25 +267,25 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_ed9c68fc9539c9df_EOF' + cat << 'GH_AW_PROMPT_0d85ce659ef2581d_EOF' - GH_AW_PROMPT_ed9c68fc9539c9df_EOF + GH_AW_PROMPT_0d85ce659ef2581d_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/repo_memory_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_ed9c68fc9539c9df_EOF' + cat << 'GH_AW_PROMPT_0d85ce659ef2581d_EOF' - Tools: add_comment(max:10), create_issue(max:4), update_issue, create_pull_request(max:3), push_to_pull_request_branch, missing_tool, missing_data, noop - GH_AW_PROMPT_ed9c68fc9539c9df_EOF + Tools: add_comment(max:10), create_issue(max:2), update_issue, create_pull_request, push_to_pull_request_branch, missing_tool, missing_data, noop + GH_AW_PROMPT_0d85ce659ef2581d_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md" - cat << 'GH_AW_PROMPT_ed9c68fc9539c9df_EOF' + cat << 'GH_AW_PROMPT_0d85ce659ef2581d_EOF' - GH_AW_PROMPT_ed9c68fc9539c9df_EOF + GH_AW_PROMPT_0d85ce659ef2581d_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_ed9c68fc9539c9df_EOF' + cat << 'GH_AW_PROMPT_0d85ce659ef2581d_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -313,12 +314,12 @@ jobs: {{/if}} - GH_AW_PROMPT_ed9c68fc9539c9df_EOF + GH_AW_PROMPT_0d85ce659ef2581d_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_ed9c68fc9539c9df_EOF' + cat << 'GH_AW_PROMPT_0d85ce659ef2581d_EOF' {{#runtime-import .github/workflows/efficiency-improver.md}} - GH_AW_PROMPT_ed9c68fc9539c9df_EOF + GH_AW_PROMPT_0d85ce659ef2581d_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -570,17 +571,17 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_4699c1cac697011a_EOF' - {"add_comment":{"hide_older_comments":true,"max":10,"target":"*"},"create_issue":{"labels":["Area: Performance","agentic-workflows"],"max":4,"title_prefix":"[efficiency-improver] "},"create_pull_request":{"draft":true,"labels":["Area: Performance","agentic-workflows"],"max":3,"max_patch_files":100,"max_patch_size":4096,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"request_review","title_prefix":"[efficiency-improver] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":102400,"max_patch_size":10240}]},"push_to_pull_request_branch":{"if_no_changes":"warn","max_patch_size":4096,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"target":"*","title_prefix":"[efficiency-improver] "},"report_incomplete":{},"update_issue":{"allow_body":true,"max":1,"target":"*"}} - GH_AW_SAFE_OUTPUTS_CONFIG_4699c1cac697011a_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_51a7f23e21542daa_EOF' + {"add_comment":{"hide_older_comments":true,"max":10,"target":"*"},"create_issue":{"labels":["Area: Performance","agentic-workflows"],"max":2,"title_prefix":"[efficiency-improver] "},"create_pull_request":{"draft":true,"labels":["Area: Performance","agentic-workflows"],"max":1,"max_patch_files":100,"max_patch_size":4096,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"request_review","title_prefix":"[efficiency-improver] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"push_repo_memory":{"memories":[{"dir":"/tmp/gh-aw/repo-memory/default","id":"default","max_file_count":100,"max_file_size":102400,"max_patch_size":10240}]},"push_to_pull_request_branch":{"if_no_changes":"warn","max_patch_size":4096,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"target":"*","title_prefix":"[efficiency-improver] "},"report_incomplete":{},"update_issue":{"allow_body":true,"max":1,"target":"*"}} + GH_AW_SAFE_OUTPUTS_CONFIG_51a7f23e21542daa_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | { "description_suffixes": { "add_comment": " CONSTRAINTS: Maximum 10 comment(s) can be added. Target: *. Supports reply_to_id for discussion threading.", - "create_issue": " CONSTRAINTS: Maximum 4 issue(s) can be created. Title will be prefixed with \"[efficiency-improver] \". Labels [\"Area: Performance\" \"agentic-workflows\"] will be automatically added.", - "create_pull_request": " CONSTRAINTS: Maximum 3 pull request(s) can be created. Title will be prefixed with \"[efficiency-improver] \". Labels [\"Area: Performance\" \"agentic-workflows\"] will be automatically added. PRs will be created as drafts.", + "create_issue": " CONSTRAINTS: Maximum 2 issue(s) can be created. Title will be prefixed with \"[efficiency-improver] \". Labels [\"Area: Performance\" \"agentic-workflows\"] will be automatically added.", + "create_pull_request": " CONSTRAINTS: Maximum 1 pull request(s) can be created. Title will be prefixed with \"[efficiency-improver] \". Labels [\"Area: Performance\" \"agentic-workflows\"] will be automatically added. PRs will be created as drafts.", "push_to_pull_request_branch": " CONSTRAINTS: The target pull request title must start with \"[efficiency-improver] \".", "update_issue": " CONSTRAINTS: Maximum 1 issue(s) can be updated. Target: *." }, @@ -1549,7 +1550,7 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: WORKFLOW_NAME: "Efficiency Improver" - WORKFLOW_DESCRIPTION: "A green-software-focused repository assistant that runs regularly (daily by default) to identify and implement\nenergy efficiency improvements. Its north-star KPI is reducing the energy consumption and\ncomputational footprint of the codebase. Always methodical, measurement-driven, and mindful of trade-offs." + WORKFLOW_DESCRIPTION: "A green-software-focused repository assistant that runs weekly to identify and implement a small number of\nhigh-impact energy efficiency improvements. Its north-star KPI is a meaningful reduction in the energy\nconsumption and computational footprint of the codebase — big wins on hot paths, not micro-optimisations.\nAlways methodical, measurement-driven, and mindful of trade-offs." HAS_PATCH: ${{ needs.agent.outputs.has_patch }} with: script: | @@ -1737,7 +1738,7 @@ jobs: await main(); - id: check run: | - MAX_OPEN_PRS=8 + MAX_OPEN_PRS=2 if [[ "$GITHUB_EVENT_NAME" != "schedule" ]]; then exit 0; fi # gh pr list exits with code 4 when --search returns no matches; treat that as 0 but # let other failures (auth, API, rate limit) propagate so we don't silently proceed. @@ -1936,7 +1937,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.vsblob.vsassets.io,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":10,\"target\":\"*\"},\"create_issue\":{\"labels\":[\"Area: Performance\",\"agentic-workflows\"],\"max\":4,\"title_prefix\":\"[efficiency-improver] \"},\"create_pull_request\":{\"draft\":true,\"labels\":[\"Area: Performance\",\"agentic-workflows\"],\"max\":3,\"max_patch_files\":100,\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"request_review\",\"title_prefix\":\"[efficiency-improver] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"target\":\"*\",\"title_prefix\":\"[efficiency-improver] \"},\"report_incomplete\":{},\"update_issue\":{\"allow_body\":true,\"max\":1,\"target\":\"*\"}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":10,\"target\":\"*\"},\"create_issue\":{\"labels\":[\"Area: Performance\",\"agentic-workflows\"],\"max\":2,\"title_prefix\":\"[efficiency-improver] \"},\"create_pull_request\":{\"draft\":true,\"labels\":[\"Area: Performance\",\"agentic-workflows\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"protected_files_policy\":\"request_review\",\"title_prefix\":\"[efficiency-improver] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"push_to_pull_request_branch\":{\"if_no_changes\":\"warn\",\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"],\"target\":\"*\",\"title_prefix\":\"[efficiency-improver] \"},\"report_incomplete\":{},\"update_issue\":{\"allow_body\":true,\"max\":1,\"target\":\"*\"}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/efficiency-improver.md b/.github/workflows/efficiency-improver.md index 0f1ba68470..c9d70c859f 100644 --- a/.github/workflows/efficiency-improver.md +++ b/.github/workflows/efficiency-improver.md @@ -1,11 +1,12 @@ --- description: | - A green-software-focused repository assistant that runs regularly (daily by default) to identify and implement - energy efficiency improvements. Its north-star KPI is reducing the energy consumption and - computational footprint of the codebase. Always methodical, measurement-driven, and mindful of trade-offs. + A green-software-focused repository assistant that runs weekly to identify and implement a small number of + high-impact energy efficiency improvements. Its north-star KPI is a meaningful reduction in the energy + consumption and computational footprint of the codebase — big wins on hot paths, not micro-optimisations. + Always methodical, measurement-driven, and mindful of trade-offs. on: - schedule: daily + schedule: weekly workflow_dispatch: reaction: "eyes" permissions: @@ -16,7 +17,7 @@ on: steps: - id: check run: | - MAX_OPEN_PRS=8 + MAX_OPEN_PRS=2 if [[ "$GITHUB_EVENT_NAME" != "schedule" ]]; then exit 0; fi # gh pr list exits with code 4 when --search returns no matches; treat that as 0 but # let other failures (auth, API, rate limit) propagate so we don't silently proceed. @@ -70,7 +71,7 @@ safe-outputs: target: "*" hide-older-comments: true create-pull-request: - max: 3 + max: 1 draft: true title-prefix: "[efficiency-improver] " labels: ["Area: Performance", "agentic-workflows"] @@ -80,7 +81,7 @@ safe-outputs: create-issue: title-prefix: "[efficiency-improver] " labels: ["Area: Performance", "agentic-workflows"] - max: 4 + max: 2 update-issue: target: "*" max: 1 @@ -96,7 +97,7 @@ tools: # Efficiency Improver -You are **Efficiency Improver** for `${{ github.repository }}`. Your job is to systematically identify and implement **energy efficiency improvements** across all dimensions of the codebase — code, data, network/I/O, and frontend/UI — with the north-star goal of **reducing the energy consumption and computational footprint** of the software. +You are **Efficiency Improver** for `${{ github.repository }}`. Your job is to identify and implement a **small number of high-impact energy efficiency improvements** across the codebase — code, data, network/I/O, and frontend/UI — with the north-star goal of a **meaningful reduction in the energy consumption and computational footprint** of the software. You deliberately ignore tiny, marginal gains: one substantial, well-measured win is the goal for a run, not a pile of micro-optimisations. You never merge pull requests yourself; you leave that decision to the human maintainers. @@ -123,6 +124,33 @@ Always be: When direct energy measurement is not possible, use these proxies and state which proxy was measured. Always note the limitations of proxy-based reasoning. +## The High-Impact Bar (read this before doing anything) + +This workflow exists to find **big** improvements, not small ones. Maintainer review time is expensive, so every PR must clear a high bar: + +- **Hot path only — and know what "hot" means here (see "Know the Workload" below).** The change must target a genuinely hot or resource-critical path. In this repo that usually means **fixed per-invocation / per-process overhead** (startup, assembly loading/JIT, IPC handshake, discovery/execution bootstrapping) — code that runs *once* per run but on *every* run — **not** per-test inner loops tuned for large test counts that rarely occur. Cold, rarely-reached code is out of scope even if technically "inefficient". +- **Large, measured effect.** Only open a PR when the change produces a substantial, reproducible improvement — as a rule of thumb, **≥15–20%** on the measured proxy for that path, or the removal of a real bottleneck (e.g. fixed startup/overhead paid on every run, an O(n²) loop that genuinely runs at large N, redundant IPC/process round-trips, or a blocking call on the critical path). If you cannot demonstrate a large effect, **do not open a PR**. +- **Worth a human's time.** Ask: "Would a maintainer be glad they reviewed this?" If the honest answer is "it's a tiny win", stop. +- **Always in scope — quadratic or worse, at any scale.** This one overrides the workload caution below: an algorithm that grows **super-linearly** — O(n²), O(n³), O(2ⁿ), or an accidentally-quadratic pattern (nested scans, `Contains`/`IndexOf` inside a loop, re-parsing or re-allocating per item, O(n²) string building) — is welcome to fix on **small or large inputs alike**. Super-linear growth is a scaling landmine: even when today's common run is tiny, it melts down as N climbs toward the 1k–10k ceiling. The complexity class itself clears the bar — you do **not** need to prove a large common-case N. + +**Explicitly out of scope — never open a PR for these:** + +- Micro-optimisations and marginal gains (single-digit-percent tweaks, hand-inlining, saving a handful of allocations off a cold path). **Exception: super-linear-growth fixes (O(n²) or worse) are always welcome — see "Always in scope" above.** +- Style-level or cosmetic "perf" changes with no measurable impact. +- Speculative changes you cannot measure. + +When you spot a promising-but-small idea, **note it in the backlog** (memory / Monthly Activity issue) instead of implementing it. Small ideas only graduate to a PR if several can be **bundled into one genuinely high-impact change** on the same hot path. + +## Know the Workload (vstest) + +Efficiency reasoning in this repo is dominated by a specific workload profile — internalise it before deciding what is "high impact": + +- **Repetition counts are small, not large.** The single most common run discovers/executes **one test**. Roughly **90% of runs are under 1000 tests**; a project holds **~1000 tests**; the practical ceiling is around **1k–10k tests** per run. Never assume large N. Many code paths run **exactly once** per invocation. +- **Fixed per-invocation overhead dominates the common case.** Because the typical run does almost no test work, the once-per-process costs — process startup, assembly loading and JIT, the vstest.console↔testhost IPC handshake, argument parsing, logger/datacollector init, discovery/execution bootstrapping — are what actually move total time and energy across real usage. **A win here helps every run, including the dominant single-test run**, and is almost always higher-impact than shaving a per-test loop. +- **Multiply by process count, not by test count.** vstest spawns testhost processes (more of them under parallel execution). Fixed startup/handshake cost **× number of processes launched** is a legitimate impact multiplier. Per-test loop iteration count usually is **not**, because N is typically tiny. +- **Discount big-N algorithmic wins unless the growth is super-linear, or you prove N is genuinely large on a common path.** A constant-factor or `O(n) → O(n)` tightening over a per-test collection is only high-impact if that code really runs at n ≈ 1k–10k in common scenarios; at n = 1 (the most common run) it saves nothing, so verify the real, common-case N before claiming impact. **Quadratic or worse is the exception** (see "Always in scope" above): O(n²), O(n³), O(2ⁿ), and accidentally-quadratic patterns are worth fixing at any scale, because the growth curve — not today's N — is the defect. +- **Measure the common case first.** Baseline the small runs — discover/run a **single test** and a ~1000-test project — not only a synthetic 10k-test benchmark. A change that only helps at 10k tests but is neutral or negative at 1 test is **low** impact for real users and does not clear the bar. + ## Focus Areas The agent concentrates on four categories of energy-related improvement: @@ -178,7 +206,7 @@ Read memory at the **start** of every run; update it at the **end**. ## Workflow -Use a **round-robin strategy**: each run, work on a different subset of tasks, rotating through them across runs so that all tasks get attention over time. Use memory to track which tasks were run most recently, and prioritise the ones that haven't run for the longest. Aim to do 2–3 tasks per run (plus the mandatory Task 7). +Use a **round-robin strategy**: each run, work on a different subset of tasks, rotating through them across runs so that all tasks get attention over time. Use memory to track which tasks were run most recently, and prioritise the ones that haven't run for the longest. Aim to do 1–2 tasks per run (plus the mandatory Task 7), and **implement at most one improvement per run** (Task 3) — and only if it clears the High-Impact Bar. It is completely fine, and often expected, for a run to produce **no PR at all**. Always do Task 7 (Update Monthly Activity Summary Issue) every run. In all comments and PR descriptions, identify yourself as "Efficiency Improver". @@ -225,11 +253,11 @@ Always do Task 7 (Update Monthly Activity Summary Issue) every run. In all comme - Look for legacy image formats and missing responsive image markup - Spot unnecessary re-renders or DOM thrashing -3. **Prioritise opportunities by estimated energy impact:** - - HIGH: Changes likely to reduce CPU time, memory, or I/O significantly (e.g., O(n²) → O(n), removing blocking I/O, eliminating redundant network calls) - - MEDIUM: Measurable but smaller gains (e.g., lazy imports, image format upgrades, adding cache headers) - - LOW: Marginal or hard-to-measure improvements (e.g., minor style changes, micro-optimisations) -4. Update memory with new opportunities found and refined priorities. Note measurement strategy for each. +3. **Prioritise opportunities by estimated energy impact — and only HIGH is actionable:** + - HIGH → **candidate for implementation** (Task 3). Changes that reduce CPU time, memory, or I/O *significantly* on a hot path (e.g., O(n²) → O(n) in a hot loop, removing blocking I/O from the critical path, eliminating redundant network round-trips). + - MEDIUM → **backlog only.** Measurable but modest gains (e.g., lazy imports, image format upgrades, adding cache headers). Record them; do **not** open a PR for them individually. + - LOW → **discard.** Marginal, cosmetic, or hard-to-measure micro-optimisations. Do not implement and do not clutter the backlog with them. +4. Update memory with new HIGH/MEDIUM opportunities found and refined priorities. Note measurement strategy for each. 5. If significant new opportunities found, create an issue summarising findings grouped by focus area. ### Task 3: Implement Energy Efficiency Improvements @@ -237,11 +265,11 @@ Always do Task 7 (Update Monthly Activity Summary Issue) every run. In all comme **Only attempt improvements you are confident about and can measure.** 1. Check memory for work in progress. Continue existing work before starting new work. -2. If starting fresh, select an optimisation goal from the backlog. Prefer: - - Goals with clear measurement strategies - - Higher estimated energy impact - - Lower-risk changes first +2. If starting fresh, select a **HIGH-impact** optimisation goal from the backlog — never a MEDIUM or LOW one. Prefer: + - Highest estimated energy impact on a genuinely hot path + - Goals with clear measurement strategies that can demonstrate a large effect - Items with maintainer interest (comments, labels) + If the backlog holds only MEDIUM/LOW items, do **not** implement anything this run — spend the time on Task 2 (finding a real high-impact opportunity) or Task 6 instead. 3. Check for existing efficiency PRs (especially yours with "[efficiency-improver]" prefix). Avoid duplicate work. 4. For the selected goal: @@ -266,7 +294,7 @@ Always do Task 7 (Update Monthly Activity Summary Issue) every run. In all comme e. Ensure the code still works — run tests. Add new tests if appropriate. - f. If no improvement: iterate, try a different approach, or revert. Record the attempt in memory as a learning. + f. If the measured improvement is absent **or merely small** (below the High-Impact Bar): do **not** open a PR. Iterate, try a different approach, or revert, and record the attempt in memory as a learning. Only a large, reproducible win earns a PR. 5. **Finalise changes**: - Apply any automatic code formatting used in the repo @@ -418,13 +446,13 @@ Maintain a single open issue titled `[efficiency-improver] Monthly Activity {YYY - **No breaking changes** without maintainer approval via a tracked issue. - **No new dependencies** without discussion in an issue first. - **Infrastructure suggestions are issue-only**: Never commit infrastructure or deployment configuration changes directly. Propose them via issues for maintainer review. -- **Small, focused PRs** — one optimisation per PR. Makes it easy to measure impact and revert if needed. +- **Focused, high-impact PRs** — one substantial optimisation per PR. Keep each PR focused so impact is easy to measure and revert, but only open it when the win is large (see The High-Impact Bar). Never open a PR for a marginal or micro-optimisation. - **Read AGENTS.md first**: before starting work on any pull request, read the repository's `AGENTS.md` file (if present) to understand project-specific conventions. - **Build, format, lint, and test before every PR**: run any code formatting, linting, and testing checks configured in the repository. Build failure, lint errors, or test failures caused by your changes → do not create the PR. Infrastructure failures → create the PR but document in the Test Status section. - **Exclude generated files from PRs**: Benchmark reports, profiler outputs, measurement results go in PR description, not in commits. - **Respect existing style** — match code formatting and naming conventions. - **AI transparency**: rely on the safe-outputs system to append the AI attribution footer to every comment, PR, and issue — do **not** add your own 🤖 disclosure header or footer. - **Anti-spam**: no repeated or follow-up comments to yourself in a single run; re-engage only when new human comments have appeared. -- **Quality over quantity**: one well-measured improvement is worth more than many unmeasured changes. +- **Quality over quantity**: one large, well-measured improvement is worth far more than many small ones. A run that ships zero PRs because nothing cleared the High-Impact Bar is a success, not a failure — do not manufacture busywork to have "something" to show. - **Document readability trade-offs**: If an optimisation makes code harder to read, explicitly acknowledge this in the PR description and justify why the energy savings warrant the trade-off. - **Reference GSF principles**: When relevant, cite Green Software Foundation principles (SCI, Energy Proportionality, Hardware Efficiency, Carbon Awareness, Demand Shaping) to give context to your findings. Don't force it — only include when it genuinely adds value. From 84b3a6c582ffc5557918b8ea2d6aecb7c33930a9 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Tue, 7 Jul 2026 13:55:26 +0100 Subject: [PATCH 26/87] Update dependencies from https://github.com/dotnet/arcade build 20260630.1 (#16217) On relative base path root Microsoft.DotNet.Arcade.Sdk From Version 11.0.0-beta.26325.1 -> To Version 11.0.0-beta.26330.1 Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.props | 2 +- eng/Version.Details.xml | 4 ++-- eng/common/core-templates/job/helix-job-monitor.yml | 9 +++++++++ eng/common/tools.sh | 2 +- global.json | 6 +++--- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 81acb719e1..bbe996e9f4 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -6,7 +6,7 @@ This file should be imported by eng/Versions.props - 11.0.0-beta.26325.1 + 11.0.0-beta.26330.1 2.0.0 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index e62bd955ed..7692eabae4 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -26,9 +26,9 @@ - + https://github.com/dotnet/arcade - b076228a542025c4f879f254d38adb5cf34a2475 + f87bce1e0d389d515282c5f74466d629ef653026 https://github.com/dotnet/symreader-converter diff --git a/eng/common/core-templates/job/helix-job-monitor.yml b/eng/common/core-templates/job/helix-job-monitor.yml index a8162c5116..96287e55a1 100644 --- a/eng/common/core-templates/job/helix-job-monitor.yml +++ b/eng/common/core-templates/job/helix-job-monitor.yml @@ -57,6 +57,14 @@ parameters: type: number default: 30 +# When 'true' (the default), Helix work items that exit 0 but have failed AzDO test results +# are treated as failed: they count toward the monitor's exit code and are resubmitted by a +# later invocation's retry pass. Set to 'false' to fall back to exit-code-only outcomes. +# Forwarded as --fail-on-failed-tests. +- name: failWorkItemsWithFailedTests + type: boolean + default: true + # Advanced: optional pipeline artifact (produced earlier in this run) that contains the tool # nupkg. When set, the artifact is downloaded and the tool is installed from the nupkg into # a local tool-path; this bypasses the repo's .config/dotnet-tools.json manifest and is @@ -170,6 +178,7 @@ jobs: toolArgs=( --helix-base-uri '${{ parameters.helixBaseUri }}' --polling-interval-seconds '${{ parameters.pollingIntervalSeconds }}' + --fail-on-failed-tests '${{ parameters.failWorkItemsWithFailedTests }}' --max-wait-minutes "$((${{ parameters.timeoutInMinutes }} - 5))" # Set the tool's timeout slightly lower than the Azure DevOps job timeout to allow it to exit gracefully. --stage-name '$(System.StageName)' ) diff --git a/eng/common/tools.sh b/eng/common/tools.sh index 69ca926a6a..3164fff333 100755 --- a/eng/common/tools.sh +++ b/eng/common/tools.sh @@ -423,7 +423,7 @@ function InitializeToolset { if [[ -z "$nuget_config" ]]; then # Search for any variation of nuget.config in the RepoRoot local found_config - found_config=$(find "$repo_root" -maxdepth 1 -type f -iname "nuget.config" -print -quit) + found_config=$(find "$repo_root" -maxdepth 1 -type f -iname nuget.config | head -n 1) if [[ -n "$found_config" ]]; then nuget_config="$found_config" diff --git a/global.json b/global.json index 22c52bd3a4..8cc2cafe04 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "11.0.100-preview.5.26227.104", + "version": "11.0.100-preview.5.26302.115", "paths": [ ".dotnet", "$host$" @@ -14,10 +14,10 @@ "vs": { "version": "17.8.0" }, - "dotnet": "11.0.100-preview.5.26227.104" + "dotnet": "11.0.100-preview.5.26302.115" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "11.0.0-beta.26325.1" + "Microsoft.DotNet.Arcade.Sdk": "11.0.0-beta.26330.1" }, "test": { "runner": "Microsoft.Testing.Platform" From 226d3fe31741fb5c3a77afb71e1c3416495721fa Mon Sep 17 00:00:00 2001 From: Azat Mukhametshin Date: Tue, 7 Jul 2026 15:02:44 +0200 Subject: [PATCH 27/87] Fix mixed synchronization for _runStartedClients in ParallelProxyExecutionManager (#16202) _runStartedClients was incremented via Interlocked.Increment from a Task.Run lambda outside any lock, while it is read inside lock (_executionStatusLockObject) in HandlePartialRunComplete. The lock did not fence the atomic write from the concurrent thread, which is formally a data race per the C# memory model. Increment the field under _executionStatusLockObject instead, matching how _runCompletedClients is handled, so the write and read are guarded by the same lock. Addresses task 4 of #16195. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Client/Parallel/ParallelProxyExecutionManager.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs index 7f21f50838..0141863382 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/Parallel/ParallelProxyExecutionManager.cs @@ -404,7 +404,14 @@ private void StartTestRunOnConcurrentManager( proxyExecutionManager.Initialize(_skipDefaultAdapters); } - Interlocked.Increment(ref _runStartedClients); + // Increment under the same lock that guards reads of this field in + // HandlePartialRunComplete, so the write is properly fenced against the + // concurrent read instead of relying on an unsynchronized Interlocked op. + lock (_executionStatusLockObject) + { + _runStartedClients++; + } + EqtTrace.Verbose("ParallelProxyExecutionManager.StartTestRunOnConcurrentManager: Initializing test run. Started clients: " + _runStartedClients); proxyExecutionManager.InitializeTestRun(testRunCriteria, eventHandler); From 6a27bdecf84bc7b6bf49f2b531f06b14daabab7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Tue, 7 Jul 2026 15:39:42 +0200 Subject: [PATCH 28/87] Exclude NuGet audit warnings from compatibility test assets (#16230) * Exclude NuGet audit warnings from compatibility test assets The generated compatibility matrix pins old Microsoft.NET.Test.Sdk and MSTest versions on purpose, and those drag in transitively vulnerable packages (Newtonsoft.Json 9.0.1/10.0.3, System.Net.Http 4.3.0, ...). A newly published advisory (GHSA-5crp-9r3c-p9vr) tripped NuGetAudit, and TreatWarningsAsErrors turned NU1903 into ~12k restore failures on main. NoWarn NU1901-NU1904 for the test assets so audit doesn't fail the compat matrix we can't upgrade by design. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- test/TestAssets/Directory.Build.props | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/TestAssets/Directory.Build.props b/test/TestAssets/Directory.Build.props index 503a3f6e1d..af14015d9a 100644 --- a/test/TestAssets/Directory.Build.props +++ b/test/TestAssets/Directory.Build.props @@ -17,8 +17,14 @@ NETSDK1057; - + CA1837; + + + NU1901;NU1902;NU1903;NU1904; From 010fd6252880ac5dddd9b5eb9a9e1daecea83e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Wed, 8 Jul 2026 14:30:31 +0200 Subject: [PATCH 29/87] Inject ITestRequestManager into vstest.console argument processors (#16228) The run and discovery argument processors reach for TestRequestManager.Instance when they build their executors, so the request orchestrator is shared process-wide static state that is reused across requests in design mode. This threads ITestRequestManager through the composition roots the same way as the IRunSettingsProvider, IRunSettingsHelper, and CommandLineOptions work in #16200, #16205, and the CommandLineOptions change, defaulting to TestRequestManager.Instance so behavior is unchanged. TestRequestManager.Instance is relatively heavy: its parameterless constructor builds a TestPlatform, a metrics publisher, and reads the design-mode flag. Today it is resolved lazily, inside each processor's Lazy, so it is only constructed when a run/discovery command actually executes and not for commands like --Help. To keep that timing byte-for-byte identical the injected instance is nullable and the processors fall back to TestRequestManager.Instance inside the lambda; the factory passes the parameter straight through without forcing it. - ArgumentProcessorFactory.Create(...) takes an optional ITestRequestManager and hands it to the six processors that build a request-manager-backed executor (ListTests, RunTests, RunSpecificTests, Port, UseVsixExtensions, and ListFullyQualifiedTests); Executor owns it and leaves it null in production so the lazy .Instance fallback keeps running. - Each of those processors holds an injected ITestRequestManager? field and uses _testRequestManager ?? TestRequestManager.Instance when it constructs its executor. - ArgumentProcessorFactoryTests builds each processor by reflection; its constructor-shape probe now covers the request-manager-bearing shapes. TestRequestManager.Instance is not obsoleted; the remaining references are the composition-root default and the lazy fallback. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/vstest.console/CommandLine/Executor.cs | 9 +++- ...istFullyQualifiedTestsArgumentProcessor.cs | 6 ++- .../Processors/ListTestsArgumentProcessor.cs | 6 ++- .../Processors/PortArgumentProcessor.cs | 6 ++- .../RunSpecificTestsArgumentProcessor.cs | 6 ++- .../Processors/RunTestsArgumentProcessor.cs | 6 ++- .../UseVsixExtensionsArgumentProcessor.cs | 6 ++- .../Utilities/ArgumentProcessorFactory.cs | 25 ++++++---- .../ArgumentProcessorFactoryTests.cs | 47 ++++++++++++------- 9 files changed, 77 insertions(+), 40 deletions(-) diff --git a/src/vstest.console/CommandLine/Executor.cs b/src/vstest.console/CommandLine/Executor.cs index f16bacdfff..4315cfea85 100644 --- a/src/vstest.console/CommandLine/Executor.cs +++ b/src/vstest.console/CommandLine/Executor.cs @@ -11,6 +11,7 @@ using System.Linq; using System.Threading; +using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; using Microsoft.VisualStudio.TestPlatform.CommandLine.Internal; using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; using Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers; @@ -65,6 +66,9 @@ internal class Executor private readonly IRunSettingsProvider _runSettingsProvider; private readonly IRunSettingsHelper _runSettingsHelper; private readonly CommandLineOptions _commandLineOptions; + // Left null in production so the argument processors resolve TestRequestManager.Instance lazily + // (only when a run/discovery command actually executes); tests inject a specific instance. + private readonly ITestRequestManager? _testRequestManager; private bool _showHelp; /// @@ -104,7 +108,7 @@ internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSour { } - internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSource, IProcessHelper processHelper, IEnvironment environment, IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper, CommandLineOptions commandLineOptions) + internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSource, IProcessHelper processHelper, IEnvironment environment, IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper, CommandLineOptions commandLineOptions, ITestRequestManager? testRequestManager = null) { DebuggerBreakpoint.AttachVisualStudioDebugger(WellKnownDebugEnvironmentVariables.VSTEST_RUNNER_DEBUG_ATTACHVS); DebuggerBreakpoint.WaitForNativeDebugger(WellKnownDebugEnvironmentVariables.VSTEST_RUNNER_NATIVE_DEBUG); @@ -118,6 +122,7 @@ internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSour _runSettingsProvider = runSettingsProvider; _runSettingsHelper = runSettingsHelper; _commandLineOptions = commandLineOptions; + _testRequestManager = testRequestManager; } /// @@ -239,7 +244,7 @@ private int GetArgumentProcessors(string[] args, out List pr { processors = new List(); int result = 0; - var processorFactory = ArgumentProcessorFactory.Create(runSettingsProvider: _runSettingsProvider, runSettingsHelper: _runSettingsHelper, commandLineOptions: _commandLineOptions); + var processorFactory = ArgumentProcessorFactory.Create(runSettingsProvider: _runSettingsProvider, runSettingsHelper: _runSettingsHelper, commandLineOptions: _commandLineOptions, testRequestManager: _testRequestManager); for (var index = 0; index < args.Length; index++) { var arg = args[index]; diff --git a/src/vstest.console/Processors/ListFullyQualifiedTestsArgumentProcessor.cs b/src/vstest.console/Processors/ListFullyQualifiedTestsArgumentProcessor.cs index 0a6bf8d0b3..2f9734850f 100644 --- a/src/vstest.console/Processors/ListFullyQualifiedTestsArgumentProcessor.cs +++ b/src/vstest.console/Processors/ListFullyQualifiedTestsArgumentProcessor.cs @@ -34,11 +34,13 @@ internal class ListFullyQualifiedTestsArgumentProcessor : IArgumentProcessor private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; private readonly CommandLineOptions _commandLineOptions; + private readonly ITestRequestManager? _testRequestManager; - public ListFullyQualifiedTestsArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider) + public ListFullyQualifiedTestsArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider, ITestRequestManager? testRequestManager = null) { _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; + _testRequestManager = testRequestManager; } /// @@ -57,7 +59,7 @@ public Lazy? Executor new ListFullyQualifiedTestsArgumentExecutor( _commandLineOptions, _runSettingsProvider, - TestRequestManager.Instance)); + _testRequestManager ?? TestRequestManager.Instance)); set => _executor = value; } diff --git a/src/vstest.console/Processors/ListTestsArgumentProcessor.cs b/src/vstest.console/Processors/ListTestsArgumentProcessor.cs index 0c61873576..546471583e 100644 --- a/src/vstest.console/Processors/ListTestsArgumentProcessor.cs +++ b/src/vstest.console/Processors/ListTestsArgumentProcessor.cs @@ -37,11 +37,13 @@ internal class ListTestsArgumentProcessor : IArgumentProcessor private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; private readonly CommandLineOptions _commandLineOptions; + private readonly ITestRequestManager? _testRequestManager; - public ListTestsArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider) + public ListTestsArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider, ITestRequestManager? testRequestManager = null) { _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; + _testRequestManager = testRequestManager; } /// @@ -60,7 +62,7 @@ public Lazy? Executor new ListTestsArgumentExecutor( _commandLineOptions, _runSettingsProvider, - TestRequestManager.Instance)); + _testRequestManager ?? TestRequestManager.Instance)); set => _executor = value; } diff --git a/src/vstest.console/Processors/PortArgumentProcessor.cs b/src/vstest.console/Processors/PortArgumentProcessor.cs index 029a543b7e..2f4b62ac68 100644 --- a/src/vstest.console/Processors/PortArgumentProcessor.cs +++ b/src/vstest.console/Processors/PortArgumentProcessor.cs @@ -32,11 +32,13 @@ internal class PortArgumentProcessor : IArgumentProcessor private Lazy? _executor; private readonly IRunSettingsHelper _runSettingsHelper; private readonly CommandLineOptions _commandLineOptions; + private readonly ITestRequestManager? _testRequestManager; - public PortArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsHelper runSettingsHelper) + public PortArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsHelper runSettingsHelper, ITestRequestManager? testRequestManager = null) { _commandLineOptions = commandLineOptions; _runSettingsHelper = runSettingsHelper; + _testRequestManager = testRequestManager; } /// @@ -51,7 +53,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new PortArgumentExecutor(_commandLineOptions, TestRequestManager.Instance, _runSettingsHelper)); + new PortArgumentExecutor(_commandLineOptions, _testRequestManager ?? TestRequestManager.Instance, _runSettingsHelper)); set => _executor = value; } diff --git a/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs b/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs index 50d5134bb3..e311c673f5 100644 --- a/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs +++ b/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs @@ -32,11 +32,13 @@ internal class RunSpecificTestsArgumentProcessor : IArgumentProcessor private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; private readonly CommandLineOptions _commandLineOptions; + private readonly ITestRequestManager? _testRequestManager; - public RunSpecificTestsArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider) + public RunSpecificTestsArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider, ITestRequestManager? testRequestManager = null) { _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; + _testRequestManager = testRequestManager; } public Lazy Metadata @@ -49,7 +51,7 @@ public Lazy? Executor new RunSpecificTestsArgumentExecutor( _commandLineOptions, _runSettingsProvider, - TestRequestManager.Instance, + _testRequestManager ?? TestRequestManager.Instance, new ArtifactProcessingManager(_commandLineOptions.TestSessionCorrelationId), ConsoleOutput.Instance)); diff --git a/src/vstest.console/Processors/RunTestsArgumentProcessor.cs b/src/vstest.console/Processors/RunTestsArgumentProcessor.cs index 90f22cc0cb..d696a8a158 100644 --- a/src/vstest.console/Processors/RunTestsArgumentProcessor.cs +++ b/src/vstest.console/Processors/RunTestsArgumentProcessor.cs @@ -28,11 +28,13 @@ internal class RunTestsArgumentProcessor : IArgumentProcessor private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; private readonly CommandLineOptions _commandLineOptions; + private readonly ITestRequestManager? _testRequestManager; - public RunTestsArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider) + public RunTestsArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider, ITestRequestManager? testRequestManager = null) { _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; + _testRequestManager = testRequestManager; } public Lazy Metadata @@ -45,7 +47,7 @@ public Lazy? Executor new RunTestsArgumentExecutor( _commandLineOptions, _runSettingsProvider, - TestRequestManager.Instance, + _testRequestManager ?? TestRequestManager.Instance, new ArtifactProcessingManager(_commandLineOptions.TestSessionCorrelationId), ConsoleOutput.Instance)); diff --git a/src/vstest.console/Processors/UseVsixExtensionsArgumentProcessor.cs b/src/vstest.console/Processors/UseVsixExtensionsArgumentProcessor.cs index f481e43c7c..0befc28427 100644 --- a/src/vstest.console/Processors/UseVsixExtensionsArgumentProcessor.cs +++ b/src/vstest.console/Processors/UseVsixExtensionsArgumentProcessor.cs @@ -27,10 +27,12 @@ internal class UseVsixExtensionsArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; private readonly CommandLineOptions _commandLineOptions; + private readonly ITestRequestManager? _testRequestManager; - public UseVsixExtensionsArgumentProcessor(CommandLineOptions commandLineOptions) + public UseVsixExtensionsArgumentProcessor(CommandLineOptions commandLineOptions, ITestRequestManager? testRequestManager = null) { _commandLineOptions = commandLineOptions; + _testRequestManager = testRequestManager; } /// @@ -46,7 +48,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new UseVsixExtensionsArgumentExecutor(_commandLineOptions, TestRequestManager.Instance, new VSExtensionManager(), ConsoleOutput.Instance)); + new UseVsixExtensionsArgumentExecutor(_commandLineOptions, _testRequestManager ?? TestRequestManager.Instance, new VSExtensionManager(), ConsoleOutput.Instance)); set => _executor = value; } diff --git a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs index 878a3bd71c..0fadcfbeeb 100644 --- a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs +++ b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs @@ -7,6 +7,7 @@ using System.Diagnostics.Contracts; using System.Linq; +using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; using Microsoft.VisualStudio.TestPlatform.Common; using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; @@ -63,13 +64,19 @@ protected ArgumentProcessorFactory(IEnumerable argumentProce /// Defaults to the ambient when not provided, so that /// callers (and the composition root) can inject an isolated instance instead of sharing static state. /// + /// + /// The test request manager that the run/discovery argument processors hand to their executors. + /// When not provided the processors fall back to the ambient + /// lazily, at the point the executor is built, so that commands that never touch it (for example + /// --Help) do not force its (relatively heavy) construction. + /// /// ArgumentProcessorFactory. - internal static ArgumentProcessorFactory Create(IFeatureFlag? featureFlag = null, IRunSettingsProvider? runSettingsProvider = null, IRunSettingsHelper? runSettingsHelper = null, CommandLineOptions? commandLineOptions = null) + internal static ArgumentProcessorFactory Create(IFeatureFlag? featureFlag = null, IRunSettingsProvider? runSettingsProvider = null, IRunSettingsHelper? runSettingsHelper = null, CommandLineOptions? commandLineOptions = null, ITestRequestManager? testRequestManager = null) { runSettingsProvider ??= RunSettingsManager.Instance; runSettingsHelper ??= RunSettingsHelper.Instance; commandLineOptions ??= CommandLineOptions.Instance; - var defaultArgumentProcessor = GetDefaultArgumentProcessors(runSettingsProvider, runSettingsHelper, commandLineOptions); + var defaultArgumentProcessor = GetDefaultArgumentProcessors(runSettingsProvider, runSettingsHelper, commandLineOptions, testRequestManager); if (!(featureFlag ?? FeatureFlag.Instance).IsSet(FeatureFlag.VSTEST_DISABLE_ARTIFACTS_POSTPROCESSING)) { @@ -203,17 +210,17 @@ public IEnumerable GetArgumentProcessorsToAlwaysExecute() .Where(lazyProcessor => lazyProcessor.Metadata.Value.IsSpecialCommand && lazyProcessor.Metadata.Value.AlwaysExecute); } - private static IList GetDefaultArgumentProcessors(IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper, CommandLineOptions commandLineOptions) => new List { + private static IList GetDefaultArgumentProcessors(IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper, CommandLineOptions commandLineOptions, ITestRequestManager? testRequestManager) => new List { new HelpArgumentProcessor(), new TestSourceArgumentProcessor(commandLineOptions), - new ListTestsArgumentProcessor(commandLineOptions, runSettingsProvider), - new RunTestsArgumentProcessor(commandLineOptions, runSettingsProvider), - new RunSpecificTestsArgumentProcessor(commandLineOptions, runSettingsProvider), + new ListTestsArgumentProcessor(commandLineOptions, runSettingsProvider, testRequestManager), + new RunTestsArgumentProcessor(commandLineOptions, runSettingsProvider, testRequestManager), + new RunSpecificTestsArgumentProcessor(commandLineOptions, runSettingsProvider, testRequestManager), new TestAdapterPathArgumentProcessor(commandLineOptions, runSettingsProvider), new TestAdapterLoadingStrategyArgumentProcessor(commandLineOptions, runSettingsProvider), new TestCaseFilterArgumentProcessor(commandLineOptions), new ParentProcessIdArgumentProcessor(commandLineOptions), - new PortArgumentProcessor(commandLineOptions, runSettingsHelper), + new PortArgumentProcessor(commandLineOptions, runSettingsHelper, testRequestManager), new RunSettingsArgumentProcessor(commandLineOptions, runSettingsProvider, runSettingsHelper), new PlatformArgumentProcessor(commandLineOptions, runSettingsProvider, runSettingsHelper), new FrameworkArgumentProcessor(commandLineOptions, runSettingsProvider), @@ -229,12 +236,12 @@ public IEnumerable GetArgumentProcessorsToAlwaysExecute() new ResponseFileArgumentProcessor(), new EnableBlameArgumentProcessor(runSettingsProvider), new AeDebuggerArgumentProcessor(), - new UseVsixExtensionsArgumentProcessor(commandLineOptions), + new UseVsixExtensionsArgumentProcessor(commandLineOptions, testRequestManager), new ListDiscoverersArgumentProcessor(), new ListExecutorsArgumentProcessor(), new ListLoggersArgumentProcessor(), new ListSettingsProvidersArgumentProcessor(), - new ListFullyQualifiedTestsArgumentProcessor(commandLineOptions, runSettingsProvider), + new ListFullyQualifiedTestsArgumentProcessor(commandLineOptions, runSettingsProvider, testRequestManager), new ListTestsTargetPathArgumentProcessor(commandLineOptions), new ShowDeprecateDotnetVStestMessageArgumentProcessor(), new EnvironmentArgumentProcessor(commandLineOptions, runSettingsProvider) diff --git a/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs b/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs index 48aa90a3a2..801003edf4 100644 --- a/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs +++ b/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; +using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; using Microsoft.VisualStudio.TestPlatform.Utilities; @@ -170,27 +171,39 @@ private static IEnumerable GetArgumentProcessors(bool specia foreach (var processor in allProcessors) { // Processors declare different constructor shapes: most now take a CommandLineOptions first, - // optionally followed by an IRunSettingsProvider and/or IRunSettingsHelper; a few legacy ones take - // only a run settings dependency, and the rest are parameterless. Pick the matching one. + // optionally followed by an IRunSettingsProvider and/or IRunSettingsHelper, and the run/discovery + // ones also take an ITestRequestManager; a few legacy ones take only a run settings dependency, and + // the rest are parameterless. Try the known shapes from most to least specific. var commandLineOptions = CommandLineOptions.Instance; var runSettingsProvider = new TestableRunSettingsProvider(); var runSettingsHelper = new RunSettingsHelper(); + var testRequestManager = new Mock().Object; + + (Type[] ParameterTypes, object[] Arguments)[] candidateConstructors = + [ + ([typeof(CommandLineOptions), typeof(IRunSettingsProvider), typeof(ITestRequestManager)], [commandLineOptions, runSettingsProvider, testRequestManager]), + ([typeof(CommandLineOptions), typeof(IRunSettingsHelper), typeof(ITestRequestManager)], [commandLineOptions, runSettingsHelper, testRequestManager]), + ([typeof(CommandLineOptions), typeof(ITestRequestManager)], [commandLineOptions, testRequestManager]), + ([typeof(CommandLineOptions), typeof(IRunSettingsProvider), typeof(IRunSettingsHelper)], [commandLineOptions, runSettingsProvider, runSettingsHelper]), + ([typeof(CommandLineOptions), typeof(IRunSettingsProvider)], [commandLineOptions, runSettingsProvider]), + ([typeof(CommandLineOptions), typeof(IRunSettingsHelper)], [commandLineOptions, runSettingsHelper]), + ([typeof(CommandLineOptions)], [commandLineOptions]), + ([typeof(IRunSettingsProvider), typeof(IRunSettingsHelper)], [runSettingsProvider, runSettingsHelper]), + ([typeof(IRunSettingsProvider)], [runSettingsProvider]), + ([typeof(IRunSettingsHelper)], [runSettingsHelper]), + ]; + + object? created = null; + foreach (var (parameterTypes, arguments) in candidateConstructors) + { + if (processor.GetConstructor(parameterTypes) is { } constructor) + { + created = constructor.Invoke(arguments); + break; + } + } - var instance = (processor.GetConstructor([typeof(CommandLineOptions), typeof(IRunSettingsProvider), typeof(IRunSettingsHelper)]) is { } optionsProviderHelperCtor - ? optionsProviderHelperCtor.Invoke([commandLineOptions, runSettingsProvider, runSettingsHelper]) - : processor.GetConstructor([typeof(CommandLineOptions), typeof(IRunSettingsProvider)]) is { } optionsProviderCtor - ? optionsProviderCtor.Invoke([commandLineOptions, runSettingsProvider]) - : processor.GetConstructor([typeof(CommandLineOptions), typeof(IRunSettingsHelper)]) is { } optionsHelperCtor - ? optionsHelperCtor.Invoke([commandLineOptions, runSettingsHelper]) - : processor.GetConstructor([typeof(CommandLineOptions)]) is { } optionsCtor - ? optionsCtor.Invoke([commandLineOptions]) - : processor.GetConstructor([typeof(IRunSettingsProvider), typeof(IRunSettingsHelper)]) is { } providerAndHelperCtor - ? providerAndHelperCtor.Invoke([runSettingsProvider, runSettingsHelper]) - : processor.GetConstructor([typeof(IRunSettingsProvider)]) is { } providerCtor - ? providerCtor.Invoke([runSettingsProvider]) - : processor.GetConstructor([typeof(IRunSettingsHelper)]) is { } helperCtor - ? helperCtor.Invoke([runSettingsHelper]) - : Activator.CreateInstance(processor)) as IArgumentProcessor; + var instance = (created ?? Activator.CreateInstance(processor)) as IArgumentProcessor; Assert.IsNotNull(instance, $"Unable to instantiate processor: {processor}"); var specialProcessor = instance.Metadata.Value.IsSpecialCommand; From 793e758e9f5e53ecf5df7d4ecf685ffa26f63e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Wed, 8 Jul 2026 14:31:31 +0200 Subject: [PATCH 30/87] Source Microsoft.TestPlatform package payloads from the real dotnet build (#16206) * Source Microsoft.TestPlatform package's .NET testhost payload from a real publish The Microsoft.TestPlatform package's TestHostNet folder (the .NET-Core testhost payload under tools\net462\...\Extensions\TestPlatform\TestHostNet\) was hand-listed from mixed per-TFM output folders: testhost.dll came from the net8.0 build output, but its Test Platform dependency assemblies and satellite resources were copied from the *net48* output ($(OutputPath)net48\...). That shipped .NET Framework-built assemblies next to a net8.0 testhost.dll.config whose binding redirects were computed for a different framework, so the DLLs on disk could disagree with the config -> FileLoadException at runtime. Introduce a "publish once, glob many" mechanism so a bundled product's DLLs and their matching .config/binding-redirects are always produced together: - eng/PublishForPackaging.targets: a product opts in with true and is published exactly once per (project, TFM) into artifacts\publish\\\ right after it builds. Gated on __ImportPackTargets==true (Arcade sets this only when packing), so a plain build.cmd without -pack does not pay for publish. The nested publish is NoBuild so it reuses fresh outputs and cannot recurse. - src/testhost: opts in for net8.0. - Microsoft.TestPlatform.csproj: the TestHostNet managed payload is now harvested wholesale from the canonical net8.0 testhost publish via a glob (_PublishNetTestHostContent), replacing ~120 hand-listed net48-sourced entries. Native/VS-internal files stay hand-listed. Verified with a clean -c Release -pack build: the Microsoft.TestPlatform package is file-count identical to baseline (553 files); binding redirects match their DLL versions across all three app.configs (vstest.console.exe, testhost.x86.exe, datacollector.exe); the TestHostNet payload DLLs now correctly report net/netstandard target frameworks (previously netframework), and eng/expected-dll-frameworks.json is updated accordingly; the RunMultipleTestAssembliesWithCodeCoverage acceptance test passes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Source the .NET Framework runner root from a real publish (Slice 2) The Microsoft.TestPlatform tool package assembled its net462 runner root (Common7\IDE\Extensions\TestPlatform\) by hand-listing ~135 files from the package project's OWN re-resolved net48 output ($(OutputPath)net48\). Because that output is produced by a different project than the one that owns each assembly, a shipped DLL and the binding redirects baked into vstest.console.exe.config could be computed from different framework resolutions - the "build and pack from different places" fragility that can break assembly load on .NET Framework hosts. vstest.console now opts into PublishForPackaging for net48, so the real 'dotnet publish' of the runner (exe + .config + full dependency closure) runs once at build time into the canonical publish root. The package's new _PublishRunnerRootContent target globs that published closure into the runner root, replacing the 135 hand-listed entries. The exe, its config and the System.* assemblies its redirects target now all come from one publish, so the layout is self-consistent by construction. Package remains 553 files (identical set). The only content change is System.Collections.Immutable.dll and System.Reflection.Metadata.dll now shipping the framework-appropriate variant the runner actually links against (same assembly versions 10.0.0.0 / 8.0.0.0; binding redirects unchanged, config byte-identical to baseline). expected-dll-frameworks.json regenerated for those two entries. Validated: verify-nupkgs (binding redirects + DLL frameworks) passes; RunMultipleTestAssembliesWithCodeCoverage acceptance test passes 2/2. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Source vstest-produced package payload from each project's build output (Slice 3) The Microsoft.TestPlatform VS-bundle package hand-listed the vstest-produced files by cherry-picking them out of the package project's single commingled $(OutputPath)net48\ folder, which aggregates every ProjectReference's output. That is the "build many deps into one folder, then piece them out" fragility: a file and its matching .config/binding-redirect could be selected from different producers. Re-source the remaining vstest-produced files from each owning project's own artifacts\bin\\\\[\] build output so every file has a single, predictable producer. Three pack-time targets replace 106 entries: - _IncludeExtensionContent: Trx/Html/TestHostRuntimeProvider/Blame/EventLog loggers - _IncludeToolContent: datacollector, arm64 runner, SettingsMigrator, DumpMinitool - _IncludeNetFrameworkTestHostContent: net462..net481 testhosts (default/x86/arm64) The dependency-helper entries are intentionally left in place (shared closure). The package file set is unchanged (553 files, identical to baseline); binding redirects and DLL target frameworks are unchanged (expected-*.json untouched). Verified with a full clean Release build+pack, eng\verify-nupkgs.ps1, and the DTA binding-redirect and code-coverage acceptance tests (all green). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update expected-dll-frameworks.json after merging main (CrossPlatEngine net8.0) main (#16201, MTP-client work) added $(NetCoreAppMinimum) to Microsoft.TestPlatform.CrossPlatEngine's TargetFrameworks. The Slice 1 publish-once TestHostNet payload (dotnet publish testhost -f net8.0) therefore now carries the net8.0 (net) flavor of CrossPlatEngine.dll instead of the netstandard2.0 one, so the expected DLL framework for tools/net462/.../TestHostNet/Microsoft.TestPlatform.CrossPlatEngine.dll flips netstandard -> net. Regenerated via build.cmd -c Release -pack (verify-nupkgs auto-update); binding redirects unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Directory.Build.props | 8 + Directory.Build.targets | 4 + eng/PublishForPackaging.targets | 55 ++ eng/expected-dll-frameworks.json | 18 +- .../Microsoft.TestPlatform.csproj | 494 ++++++------------ src/testhost/testhost.csproj | 8 + src/vstest.console/vstest.console.csproj | 7 + 7 files changed, 255 insertions(+), 339 deletions(-) create mode 100644 eng/PublishForPackaging.targets diff --git a/Directory.Build.props b/Directory.Build.props index a5744bc1ee..8d2ca5fec7 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -9,6 +9,14 @@ false true $(RepoRoot)src\package\ + + $(ArtifactsDir)publish\ enable false diff --git a/Directory.Build.targets b/Directory.Build.targets index d4ec3b2c27..abf0b4501d 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -17,6 +17,10 @@ + + + + + + + + + <_PackagingPublishDir>$(TestPlatformPackagingPublishRoot)$(MSBuildProjectName)\$(TargetFramework)\ + + + + + + + + + + diff --git a/eng/expected-dll-frameworks.json b/eng/expected-dll-frameworks.json index 77af3cb9d8..093c51c078 100644 --- a/eng/expected-dll-frameworks.json +++ b/eng/expected-dll-frameworks.json @@ -136,18 +136,18 @@ "tools/net462/Common7/IDE/Extensions/TestPlatform/pt-BR/Microsoft.CodeCoverage.IO.dll": "none", "tools/net462/Common7/IDE/Extensions/TestPlatform/ru/Microsoft.CodeCoverage.IO.dll": "none", "tools/net462/Common7/IDE/Extensions/TestPlatform/System.Buffers.dll": "netframework", - "tools/net462/Common7/IDE/Extensions/TestPlatform/System.Collections.Immutable.dll": "netstandard", + "tools/net462/Common7/IDE/Extensions/TestPlatform/System.Collections.Immutable.dll": "netframework", "tools/net462/Common7/IDE/Extensions/TestPlatform/System.Memory.dll": "netframework", "tools/net462/Common7/IDE/Extensions/TestPlatform/System.Numerics.Vectors.dll": "netframework", - "tools/net462/Common7/IDE/Extensions/TestPlatform/System.Reflection.Metadata.dll": "netstandard", + "tools/net462/Common7/IDE/Extensions/TestPlatform/System.Reflection.Metadata.dll": "netframework", "tools/net462/Common7/IDE/Extensions/TestPlatform/System.Runtime.CompilerServices.Unsafe.dll": "none", - "tools/net462/Common7/IDE/Extensions/TestPlatform/TestHostNet/Microsoft.TestPlatform.CommunicationUtilities.dll": "netframework", - "tools/net462/Common7/IDE/Extensions/TestPlatform/TestHostNet/Microsoft.TestPlatform.CoreUtilities.dll": "netframework", - "tools/net462/Common7/IDE/Extensions/TestPlatform/TestHostNet/Microsoft.TestPlatform.CrossPlatEngine.dll": "netframework", - "tools/net462/Common7/IDE/Extensions/TestPlatform/TestHostNet/Microsoft.TestPlatform.PlatformAbstractions.dll": "netframework", - "tools/net462/Common7/IDE/Extensions/TestPlatform/TestHostNet/Microsoft.TestPlatform.Utilities.dll": "netframework", - "tools/net462/Common7/IDE/Extensions/TestPlatform/TestHostNet/Microsoft.VisualStudio.TestPlatform.Common.dll": "netframework", - "tools/net462/Common7/IDE/Extensions/TestPlatform/TestHostNet/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll": "netframework", + "tools/net462/Common7/IDE/Extensions/TestPlatform/TestHostNet/Microsoft.TestPlatform.CommunicationUtilities.dll": "net", + "tools/net462/Common7/IDE/Extensions/TestPlatform/TestHostNet/Microsoft.TestPlatform.CoreUtilities.dll": "net", + "tools/net462/Common7/IDE/Extensions/TestPlatform/TestHostNet/Microsoft.TestPlatform.CrossPlatEngine.dll": "net", + "tools/net462/Common7/IDE/Extensions/TestPlatform/TestHostNet/Microsoft.TestPlatform.PlatformAbstractions.dll": "net", + "tools/net462/Common7/IDE/Extensions/TestPlatform/TestHostNet/Microsoft.TestPlatform.Utilities.dll": "netstandard", + "tools/net462/Common7/IDE/Extensions/TestPlatform/TestHostNet/Microsoft.VisualStudio.TestPlatform.Common.dll": "netstandard", + "tools/net462/Common7/IDE/Extensions/TestPlatform/TestHostNet/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll": "net", "tools/net462/Common7/IDE/Extensions/TestPlatform/TestHostNet/Microsoft.VisualStudio.TestTools.CppUnitTestFramework.ComInterfaces.dll": "netframework", "tools/net462/Common7/IDE/Extensions/TestPlatform/TestHostNet/Microsoft.VisualStudio.TestTools.CppUnitTestFramework.CppUnitTestExtension.dll": "netframework", "tools/net462/Common7/IDE/Extensions/TestPlatform/TestHostNet/Microsoft.VisualStudio.TestTools.CppUnitTestFramework.Discoverer.dll": "netframework", diff --git a/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.csproj b/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.csproj index 3e5beaa1e5..03ae13c436 100644 --- a/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.csproj +++ b/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.csproj @@ -28,7 +28,7 @@ - $(TargetsForTfmSpecificContentInPackage);_IncludeInternalCodeCoverageContent + $(TargetsForTfmSpecificContentInPackage);_IncludeInternalCodeCoverageContent;_PublishNetTestHostContent;_PublishRunnerRootContent;_IncludeExtensionContent;_IncludeToolContent;_IncludeNetFrameworkTestHostContent Microsoft.TestPlatform vstest visual-studio unittest testplatform mstest microsoft test testing @@ -187,127 +187,16 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -359,154 +248,17 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -538,89 +290,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -657,4 +328,167 @@ + + + + + + <_NetTestHostPublishDir>$(TestPlatformPackagingPublishRoot)testhost\$(NetCoreAppMinimum)\ + + + + + <_NetTestHostPublishedFile Include="$(_NetTestHostPublishDir)**\*" Exclude="$(_NetTestHostPublishDir)**\*.xml;$(_NetTestHostPublishDir)testhost.exe;$(_NetTestHostPublishDir)testhost.runtimeconfig.json" /> + + tools\net462\Common7\IDE\Extensions\TestPlatform\TestHostNet\%(RecursiveDir)%(Filename)%(Extension) + + + + + + + + <_RunnerRootPublishDir>$(TestPlatformPackagingPublishRoot)vstest.console\$(NetFrameworkRunnerTargetFramework)\ + + + + <_RunnerRootPublishedFile Include="$(_RunnerRootPublishDir)**\*" Exclude="$(_RunnerRootPublishDir)**\*.xml;$(_RunnerRootPublishDir)System.ValueTuple.dll" /> + + tools\net462\Common7\IDE\Extensions\TestPlatform\%(RecursiveDir)%(Filename)%(Extension) + + + + + + + <_TrxLoggerContent Include="$(ArtifactsBinDir)Microsoft.TestPlatform.Extensions.TrxLogger\$(Configuration)\net462\**\Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger*.dll"> + tools\net462\Common7\IDE\Extensions\TestPlatform\Extensions\%(RecursiveDir)%(Filename)%(Extension) + + <_HtmlLoggerContent Include="$(ArtifactsBinDir)Microsoft.TestPlatform.Extensions.HtmlLogger\$(Configuration)\net48\**\Microsoft.VisualStudio.TestPlatform.Extensions.Html.TestLogger*.dll"> + tools\net462\Common7\IDE\Extensions\TestPlatform\Extensions\%(RecursiveDir)%(Filename)%(Extension) + + <_TestHostRuntimeProviderContent Include="$(ArtifactsBinDir)Microsoft.TestPlatform.TestHostProvider\$(Configuration)\net48\**\Microsoft.TestPlatform.TestHostRuntimeProvider*.dll"> + tools\net462\Common7\IDE\Extensions\TestPlatform\Extensions\%(RecursiveDir)%(Filename)%(Extension) + + <_BlameDataCollectorContent Include="$(ArtifactsBinDir)Microsoft.TestPlatform.Extensions.BlameDataCollector\$(Configuration)\net48\Microsoft.TestPlatform.Extensions.BlameDataCollector.dll"> + tools\net462\Common7\IDE\Extensions\TestPlatform\Extensions\%(Filename)%(Extension) + + <_EventLogCollectorContent Include="$(ArtifactsBinDir)Microsoft.TestPlatform.Extensions.EventLogCollector\$(Configuration)\net48\Microsoft.TestPlatform.Extensions.EventLogCollector.dll"> + tools\net462\Common7\IDE\Extensions\TestPlatform\Extensions\%(Filename)%(Extension) + + + + + + + + <_DataCollectorContent Include="$(ArtifactsBinDir)datacollector\$(Configuration)\net48\win7-x64\datacollector.exe;$(ArtifactsBinDir)datacollector\$(Configuration)\net48\win7-x64\datacollector.exe.config;$(ArtifactsBinDir)datacollector.arm64\$(Configuration)\net48\win10-arm64\datacollector.arm64.exe;$(ArtifactsBinDir)datacollector.arm64\$(Configuration)\net48\win10-arm64\datacollector.arm64.exe.config"> + tools\net462\Common7\IDE\Extensions\TestPlatform\%(Filename)%(Extension) + + <_VsTestConsoleArm64Content Include="$(ArtifactsBinDir)vstest.console.arm64\$(Configuration)\net48\win10-arm64\vstest.console.arm64.exe;$(ArtifactsBinDir)vstest.console.arm64\$(Configuration)\net48\win10-arm64\vstest.console.arm64.exe.config"> + tools\net462\Common7\IDE\Extensions\TestPlatform\%(Filename)%(Extension) + + <_SettingsMigratorContent Include="$(ArtifactsBinDir)SettingsMigrator\$(Configuration)\net48\win7-x64\SettingsMigrator.exe"> + tools\net462\Common7\IDE\Extensions\TestPlatform\%(Filename)%(Extension) + + <_SettingsMigratorSatelliteContent Include="$(ArtifactsBinDir)SettingsMigrator\$(Configuration)\net48\win7-x64\**\SettingsMigrator.resources.dll"> + tools\net462\Common7\IDE\Extensions\TestPlatform\%(RecursiveDir)%(Filename)%(Extension) + + <_DumpMinitoolContent Include="$(ArtifactsBinDir)DumpMinitool\$(Configuration)\net462\win7-x64\DumpMinitool.exe;$(ArtifactsBinDir)DumpMinitool\$(Configuration)\net462\win7-x64\DumpMinitool.exe.config;$(ArtifactsBinDir)DumpMinitool.x86\$(Configuration)\net462\win-x86\DumpMinitool.x86.exe;$(ArtifactsBinDir)DumpMinitool.x86\$(Configuration)\net462\win-x86\DumpMinitool.x86.exe.config;$(ArtifactsBinDir)DumpMinitool.arm64\$(Configuration)\net462\win10-arm64\DumpMinitool.arm64.exe;$(ArtifactsBinDir)DumpMinitool.arm64\$(Configuration)\net462\win10-arm64\DumpMinitool.arm64.exe.config"> + tools\net462\Common7\IDE\Extensions\TestPlatform\Extensions\dump\%(Filename)%(Extension) + + + + + + + + + <_NetFrameworkTestHostContent Include="$(ArtifactsBinDir)testhost\$(Configuration)\net462\win7-x64\testhost.exe;$(ArtifactsBinDir)testhost\$(Configuration)\net462\win7-x64\testhost.exe.config;$(ArtifactsBinDir)testhost\$(Configuration)\net47\win7-x64\testhost.net47.exe;$(ArtifactsBinDir)testhost\$(Configuration)\net47\win7-x64\testhost.net47.exe.config;$(ArtifactsBinDir)testhost\$(Configuration)\net471\win7-x64\testhost.net471.exe;$(ArtifactsBinDir)testhost\$(Configuration)\net471\win7-x64\testhost.net471.exe.config;$(ArtifactsBinDir)testhost\$(Configuration)\net472\win7-x64\testhost.net472.exe;$(ArtifactsBinDir)testhost\$(Configuration)\net472\win7-x64\testhost.net472.exe.config;$(ArtifactsBinDir)testhost\$(Configuration)\net48\win7-x64\testhost.net48.exe;$(ArtifactsBinDir)testhost\$(Configuration)\net48\win7-x64\testhost.net48.exe.config;$(ArtifactsBinDir)testhost\$(Configuration)\net481\win7-x64\testhost.net481.exe;$(ArtifactsBinDir)testhost\$(Configuration)\net481\win7-x64\testhost.net481.exe.config"> + tools\net462\Common7\IDE\Extensions\TestPlatform\%(Filename)%(Extension) + + <_NetFrameworkTestHostX86Content Include="$(ArtifactsBinDir)testhost.x86\$(Configuration)\net462\win-x86\testhost.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net462\win-x86\testhost.x86.exe.config;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net47\win-x86\testhost.net47.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net47\win-x86\testhost.net47.x86.exe.config;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net471\win-x86\testhost.net471.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net471\win-x86\testhost.net471.x86.exe.config;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net472\win-x86\testhost.net472.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net472\win-x86\testhost.net472.x86.exe.config;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net48\win-x86\testhost.net48.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net48\win-x86\testhost.net48.x86.exe.config;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net481\win-x86\testhost.net481.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net481\win-x86\testhost.net481.x86.exe.config"> + tools\net462\Common7\IDE\Extensions\TestPlatform\%(Filename)%(Extension) + + <_NetFrameworkTestHostArm64Content Include="$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net462\win10-arm64\testhost.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net462\win10-arm64\testhost.arm64.exe.config;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net47\win10-arm64\testhost.net47.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net47\win10-arm64\testhost.net47.arm64.exe.config;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net471\win10-arm64\testhost.net471.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net471\win10-arm64\testhost.net471.arm64.exe.config;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net472\win10-arm64\testhost.net472.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net472\win10-arm64\testhost.net472.arm64.exe.config;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net48\win10-arm64\testhost.net48.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net48\win10-arm64\testhost.net48.arm64.exe.config;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net481\win10-arm64\testhost.net481.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net481\win10-arm64\testhost.net481.arm64.exe.config"> + tools\net462\Common7\IDE\Extensions\TestPlatform\%(Filename)%(Extension) + + + + diff --git a/src/testhost/testhost.csproj b/src/testhost/testhost.csproj index 05f048d7a1..f38a65dc9c 100644 --- a/src/testhost/testhost.csproj +++ b/src/testhost/testhost.csproj @@ -18,6 +18,14 @@ app.manifest + + + true + + win7-x64 false diff --git a/src/vstest.console/vstest.console.csproj b/src/vstest.console/vstest.console.csproj index 262e80a41a..b3229692e5 100644 --- a/src/vstest.console/vstest.console.csproj +++ b/src/vstest.console/vstest.console.csproj @@ -31,6 +31,13 @@ win7-x64 + + + + true + From 051e438093e1e07f3a8c6858b94521260edd9556 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Wed, 8 Jul 2026 16:52:00 +0200 Subject: [PATCH 31/87] Forward per-test-case events to the datacollector for MTP runs (#16235) When an MTP application runs under vstest.console there is no testhost, so nobody connects to the datacollector's per-test-case event channel. Collectors that rely on it, most visibly Blame, blocked until the connection timeout (~90s) on every run, and never learned which test was running when the app crashed. vstest.console already observes every test-case start and end from the MTP node updates, it just kept them to itself. This connects to the datacollector from the console and forwards those started/ended notifications, reusing the same DataCollectionTestCaseEventSender, ProxyOutOfProcDataCollectionManager and TestCaseEventsHandler that testhost uses in the classic path. Blame under MTP no longer hangs (~90s down to ~2s) and per-test events reach the datacollector for tests that complete. Crash attribution works when the test runs long enough for MTP to flush its in-progress update; an instant self-kill still races that flush and is not attributed, the same limitation the in-progress node update itself has. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Client/MTP/MtpDataCollectionForwarder.cs | 174 ++++++++++++++++++ .../Client/MTP/MtpProxyExecutionManager.cs | 60 +++++- .../MtpUnderVstestTests.cs | 26 +++ 3 files changed, 258 insertions(+), 2 deletions(-) create mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpDataCollectionForwarder.cs diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpDataCollectionForwarder.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpDataCollectionForwarder.cs new file mode 100644 index 0000000000..fefa2125dc --- /dev/null +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpDataCollectionForwarder.cs @@ -0,0 +1,174 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; + +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; +using Microsoft.VisualStudio.TestPlatform.CoreUtilities.Helpers; +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection; +using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.EventHandlers; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; + +namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.MTP; + +/// +/// Forwards per-test-case "started"/"ended" notifications from a Microsoft.Testing.Platform (MTP) +/// application to the out-of-process datacollector, over the same socket sub-channel that testhost +/// uses in the classic run path. +/// +/// +/// In the classic path testhost owns this connection: the datacollector opens a socket, testhost +/// dials in via and pushes +/// TestCaseStart/TestCaseEnd events so collectors such as Blame can track which test is +/// currently running. Under MTP there is no testhost — vstest.console drives the MTP application +/// directly and is the only party that observes per-test-case state — so nobody connects to that +/// socket and the datacollector blocks for its full connection timeout (~90s) before giving up, and +/// collectors never learn which test crashed. +/// +/// +/// +/// This class closes that gap. It reuses the very same , +/// and publisher +/// that testhost uses, but is driven by MTP node updates instead of the adapter: as the run progresses +/// it raises test-case start/end, test-result and session-end notifications through the publisher, which +/// the out-of-proc manager relays to the datacollector. +/// +/// +internal sealed class MtpDataCollectionForwarder : IDisposable +{ + private readonly DataCollectionTestCaseEventSender _sender; + private readonly object _syncObject = new(); + private readonly HashSet _startedTests = new(); + + // The classic publisher used by testhost. ProxyOutOfProcDataCollectionManager subscribes to it in + // its constructor and forwards the events to the datacollector through _sender; we raise them by + // calling the publisher's Send* methods as MTP node updates arrive. + private readonly TestCaseEventsHandler _publisher; + private readonly ProxyOutOfProcDataCollectionManager _outOfProcManager; + + private bool _connected; + private bool _disposed; + + public MtpDataCollectionForwarder() + { + _sender = DataCollectionTestCaseEventSender.Create(); + _publisher = new TestCaseEventsHandler(); + _outOfProcManager = new ProxyOutOfProcDataCollectionManager(_sender, _publisher); + } + + /// + /// Connects to the datacollector's test-case event socket on the given port. Returns + /// on success. On failure the run continues without event forwarding + /// (data collectors that need per-test-case events, e.g. Blame, will not function, but the run + /// itself is not aborted). + /// + public bool Connect(int port) + { + try + { + _sender.InitializeCommunication(port); + var timeout = EnvironmentHelper.GetConnectionTimeout(); + _connected = _sender.WaitForRequestSenderConnection(timeout * 1000); + if (!_connected) + { + EqtTrace.Error("MtpDataCollectionForwarder.Connect: timed out connecting to the datacollector on port {0}.", port); + } + } + catch (Exception ex) + { + EqtTrace.Error("MtpDataCollectionForwarder.Connect: failed to connect to the datacollector on port {0}: {1}", port, ex); + _connected = false; + } + + return _connected; + } + + /// + /// Notifies the datacollector that a test case has started. Idempotent per test id. + /// + public void NotifyTestCaseStart(TestCase testCase) + { + if (!_connected) + { + return; + } + + lock (_syncObject) + { + if (!_startedTests.Add(testCase.Id)) + { + return; + } + } + + SafeInvoke(() => _publisher.SendTestCaseStart(testCase)); + } + + /// + /// Notifies the datacollector that a test case has ended. Ensures a matching start was sent first, + /// so collectors that key end events off a prior start (e.g. Blame) never see an orphan end. + /// + public void NotifyTestCaseEnd(TestResult result) + { + if (!_connected) + { + return; + } + + NotifyTestCaseStart(result.TestCase); + SafeInvoke(() => _publisher.SendTestCaseEnd(result.TestCase, result.Outcome)); + + // Give the out-of-proc manager the result so any attachments produced by TestCaseEnd are + // merged into it, matching the classic path. + SafeInvoke(() => _publisher.SendTestResult(result)); + } + + /// + /// Notifies the datacollector that the session has ended, releasing its wait on the test-case + /// event channel so it can finalize without hitting the connection timeout. + /// + public void NotifySessionEnd() + { + if (!_connected) + { + return; + } + + SafeInvoke(() => _publisher.SendSessionEnd()); + } + + private void SafeInvoke(Action action) + { + try + { + action(); + } + catch (Exception ex) + { + // A failure on the datacollector sub-channel must not take the whole run down. Stop + // forwarding and let the run finish; the datacollector's own timeout is the backstop. + EqtTrace.Error("MtpDataCollectionForwarder: error forwarding a test-case event, disabling forwarding: {0}", ex); + _connected = false; + } + } + + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + + try + { + _sender.Close(); + } + catch (Exception ex) + { + EqtTrace.Warning("MtpDataCollectionForwarder.Dispose: error closing the sender: {0}", ex); + } + } +} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyExecutionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyExecutionManager.cs index fc4363a832..da5b6d0726 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyExecutionManager.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyExecutionManager.cs @@ -37,6 +37,13 @@ internal sealed class MtpProxyExecutionManager : IProxyExecutionManager, IDispos private readonly DataCollectionRunEventsHandler? _dataCollectionEventsHandler; + /// + /// Forwards per-test-case started/ended notifications (observed from the MTP application) to the + /// out-of-process datacollector. Created only when a data collector asks for test-case-level + /// events (e.g. Blame); left null for code coverage or when data collection is off. + /// + private MtpDataCollectionForwarder? _testCaseEventForwarder; + private bool _isInitialized; public MtpProxyExecutionManager() @@ -154,6 +161,24 @@ private void BeforeTestRun(IInternalTestRunEventsHandler eventHandler) } } + // If a data collector needs per-test-case events (e.g. Blame tracks the currently running + // test to attribute crashes), it opens a socket and returns its port. In the classic path + // testhost connects to it; under MTP there is no testhost, so we connect from here and + // forward the started/ended notifications we observe from the MTP application. A port of 0 + // means no collector needs these events (e.g. code coverage) and we do nothing. + if (parameters?.DataCollectionEventsPort > 0) + { + _testCaseEventForwarder = new MtpDataCollectionForwarder(); + if (!_testCaseEventForwarder.Connect(parameters.DataCollectionEventsPort)) + { + eventHandler.HandleLogMessage( + ObjectModel.Logging.TestMessageLevel.Warning, + "Could not connect to the data collector for per-test-case events; collectors that rely on them (such as Blame) may not function for this Microsoft.Testing.Platform run."); + _testCaseEventForwarder.Dispose(); + _testCaseEventForwarder = null; + } + } + // Surface any messages the data collector produced while starting up. foreach (Tuple message in _dataCollectionEventsHandler!.Messages) { @@ -174,6 +199,10 @@ private void AfterTestRun(List attachments, List attachments, List Date: Wed, 8 Jul 2026 17:24:12 +0200 Subject: [PATCH 32/87] Source Microsoft.TestPlatform.CLI package payloads from the real dotnet build (#16236) The Microsoft.TestPlatform.CLI package hand-listed ~344 product assemblies by cherry-picking them out of THIS package project's commingled per-TFM output folder, into which all 15 ProjectReferences (vstest.console, testhost/.x86/.arm64, datacollector/.arm64, DumpMinitool*, the loggers, TestHostProvider) copy their outputs. A shipped DLL and its matching .config / binding redirects could therefore be produced by different framework resolutions - the same hazard #16206 fixed for the Microsoft.TestPlatform package. Apply the same "publish/build once, glob many" pattern so every payload file has a single, self-consistent producer: - src/vstest.console: opts into net10.0 publish; the CLI net10.0 contentFiles root is harvested wholesale from that publish (_IncludeCliRunnerContent). - src/testhost: opts into net462 publish; the TestHostNetFramework default-arch closure is harvested from that publish (_IncludeCliNetFrameworkTestHostPublishContent). - The remaining product assemblies (net testhost, datacollector, the Extensions loggers/blame/provider, DumpMinitool, and the per-arch/per-TFM renamed testhost and datacollector executables) are gathered at pack time from each producing project's OWN build output via TargetsForTfmSpecificContentInPackage targets. - Externals raked in by CopyFiles, the generated testhost runtimeconfig templates, and the .NET Framework facade assemblies (which no product project produces) stay hand-listed. Verified with a clean -c Release -pack build: the CLI package file set is identical to baseline (+0/-0). System.Collections.Immutable.dll under TestHostNetFramework\ now resolves as the .NET Framework build (from the testhost net462 publish) instead of whichever flavor won the commingled-folder race; it is version 10.0.0.0, matching the binding redirect in every co-shipped testhost/datacollector .config. That single expected reclassification is recorded in eng/expected-dll-frameworks.json. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- eng/expected-dll-frameworks.json | 2 +- .../Microsoft.TestPlatform.CLI.csproj | 599 +++++++----------- src/testhost/testhost.csproj | 9 + src/vstest.console/vstest.console.csproj | 7 + 4 files changed, 250 insertions(+), 367 deletions(-) diff --git a/eng/expected-dll-frameworks.json b/eng/expected-dll-frameworks.json index 093c51c078..02bf52c13f 100644 --- a/eng/expected-dll-frameworks.json +++ b/eng/expected-dll-frameworks.json @@ -203,7 +203,7 @@ "contentFiles/any/net10.0/TestHostNetFramework/System.Buffers.dll": "netframework", "contentFiles/any/net10.0/TestHostNetFramework/System.Collections.Concurrent.dll": "none", "contentFiles/any/net10.0/TestHostNetFramework/System.Collections.dll": "none", - "contentFiles/any/net10.0/TestHostNetFramework/System.Collections.Immutable.dll": "netstandard", + "contentFiles/any/net10.0/TestHostNetFramework/System.Collections.Immutable.dll": "netframework", "contentFiles/any/net10.0/TestHostNetFramework/System.Collections.NonGeneric.dll": "none", "contentFiles/any/net10.0/TestHostNetFramework/System.Collections.Specialized.dll": "none", "contentFiles/any/net10.0/TestHostNetFramework/System.ComponentModel.dll": "none", diff --git a/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.csproj b/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.csproj index 2f1d93240a..6fbcc83013 100644 --- a/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.csproj +++ b/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.csproj @@ -37,26 +37,20 @@ - + - - - - + - - - - - - - - - + @@ -71,82 +65,26 @@ - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - @@ -184,7 +122,6 @@ - @@ -196,16 +133,13 @@ - - - @@ -235,7 +169,6 @@ - @@ -244,279 +177,13 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + $(TargetsForTfmSpecificContentInPackage);_IncludeCliRunnerContent;_IncludeCliNetTestHostContent;_IncludeCliDataCollectorContent;_IncludeCliExtensionContent;_IncludeCliDumpContent;_IncludeCliNetFrameworkTestHostPublishContent;_IncludeCliNetFrameworkTestHostContent + + + + + + <_CliRunnerPublishDir>$(TestPlatformPackagingPublishRoot)vstest.console\$(NetSDKTargetFramework)\ + + + + <_CliRunnerPublishedFile Include="$(_CliRunnerPublishDir)**\*" + Exclude="$(_CliRunnerPublishDir)**\*.xml;$(_CliRunnerPublishDir)Microsoft.Extensions.FileSystemGlobbing.dll" /> + + contentFiles\any\net10.0\%(RecursiveDir)%(Filename)%(Extension) + None + true + false + + + + + + + + <_CliNetTestHostContent Include="$(ArtifactsBinDir)testhost\$(Configuration)\$(NetCoreAppMinimum)\testhost.dll;$(ArtifactsBinDir)testhost\$(Configuration)\$(NetCoreAppMinimum)\testhost.deps.json"> + contentFiles\any\net10.0\%(Filename)%(Extension) + None + true + false + + + + + + + + + <_CliDataCollectorContent Include="$(ArtifactsBinDir)datacollector\$(Configuration)\$(NetSDKTargetFramework)\datacollector.dll;$(ArtifactsBinDir)datacollector\$(Configuration)\$(NetSDKTargetFramework)\datacollector.dll.config;$(ArtifactsBinDir)datacollector\$(Configuration)\$(NetSDKTargetFramework)\datacollector.deps.json;$(ArtifactsBinDir)datacollector\$(Configuration)\$(NetSDKTargetFramework)\datacollector.runtimeconfig.json"> + contentFiles\any\net10.0\%(Filename)%(Extension) + None + true + false + + + + + + + + + <_CliBlameContent Include="$(ArtifactsBinDir)Microsoft.TestPlatform.Extensions.BlameDataCollector\$(Configuration)\netstandard2.0\**\Microsoft.TestPlatform.Extensions.BlameDataCollector*.dll"> + contentFiles\any\net10.0\Extensions\%(RecursiveDir)%(Filename)%(Extension) + None + true + false + + <_CliEventLogContent Include="$(ArtifactsBinDir)Microsoft.TestPlatform.Extensions.EventLogCollector\$(Configuration)\net48\**\Microsoft.TestPlatform.Extensions.EventLogCollector*.dll"> + contentFiles\any\net10.0\Extensions\%(RecursiveDir)%(Filename)%(Extension) + None + true + false + + <_CliHtmlContent Include="$(ArtifactsBinDir)Microsoft.TestPlatform.Extensions.HtmlLogger\$(Configuration)\netstandard2.0\**\Microsoft.VisualStudio.TestPlatform.Extensions.Html.TestLogger*.dll"> + contentFiles\any\net10.0\Extensions\%(RecursiveDir)%(Filename)%(Extension) + None + true + false + + <_CliTrxContent Include="$(ArtifactsBinDir)Microsoft.TestPlatform.Extensions.TrxLogger\$(Configuration)\netstandard2.0\**\Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger*.dll"> + contentFiles\any\net10.0\Extensions\%(RecursiveDir)%(Filename)%(Extension) + None + true + false + + <_CliTestHostProviderContent Include="$(ArtifactsBinDir)Microsoft.TestPlatform.TestHostProvider\$(Configuration)\netstandard2.0\**\Microsoft.TestPlatform.TestHostRuntimeProvider*.dll"> + contentFiles\any\net10.0\Extensions\%(RecursiveDir)%(Filename)%(Extension) + None + true + false + + + + + + + + + <_CliDumpContent Include="$(ArtifactsBinDir)DumpMinitool\$(Configuration)\$(NetFrameworkMinimum)\win7-x64\DumpMinitool.exe;$(ArtifactsBinDir)DumpMinitool\$(Configuration)\$(NetFrameworkMinimum)\win7-x64\DumpMinitool.exe.config;$(ArtifactsBinDir)DumpMinitool.x86\$(Configuration)\$(NetFrameworkMinimum)\win-x86\DumpMinitool.x86.exe;$(ArtifactsBinDir)DumpMinitool.x86\$(Configuration)\$(NetFrameworkMinimum)\win-x86\DumpMinitool.x86.exe.config;$(ArtifactsBinDir)DumpMinitool.arm64\$(Configuration)\$(NetFrameworkMinimum)\win10-arm64\DumpMinitool.arm64.exe;$(ArtifactsBinDir)DumpMinitool.arm64\$(Configuration)\$(NetFrameworkMinimum)\win10-arm64\DumpMinitool.arm64.exe.config"> + contentFiles\any\net10.0\Extensions\dump\%(Filename)%(Extension) + None + true + false + + + + + + + + + <_CliNetFrameworkTestHostPublishDir>$(TestPlatformPackagingPublishRoot)testhost\$(NetFrameworkMinimum)\ + + + + <_CliNetFrameworkTestHostPublishedFile Include="$(_CliNetFrameworkTestHostPublishDir)**\*" + Exclude="$(_CliNetFrameworkTestHostPublishDir)**\*.xml" /> + + contentFiles\any\net10.0\TestHostNetFramework\%(RecursiveDir)%(Filename)%(Extension) + None + true + false + + + + + + + + <_CliNetFrameworkTestHostDefault Include="$(ArtifactsBinDir)testhost\$(Configuration)\net47\win7-x64\testhost.net47.exe;$(ArtifactsBinDir)testhost\$(Configuration)\net47\win7-x64\testhost.net47.exe.config;$(ArtifactsBinDir)testhost\$(Configuration)\net471\win7-x64\testhost.net471.exe;$(ArtifactsBinDir)testhost\$(Configuration)\net471\win7-x64\testhost.net471.exe.config;$(ArtifactsBinDir)testhost\$(Configuration)\net472\win7-x64\testhost.net472.exe;$(ArtifactsBinDir)testhost\$(Configuration)\net472\win7-x64\testhost.net472.exe.config;$(ArtifactsBinDir)testhost\$(Configuration)\net48\win7-x64\testhost.net48.exe;$(ArtifactsBinDir)testhost\$(Configuration)\net48\win7-x64\testhost.net48.exe.config;$(ArtifactsBinDir)testhost\$(Configuration)\net481\win7-x64\testhost.net481.exe;$(ArtifactsBinDir)testhost\$(Configuration)\net481\win7-x64\testhost.net481.exe.config"> + contentFiles\any\net10.0\TestHostNetFramework\%(Filename)%(Extension) + None + true + false + + <_CliNetFrameworkTestHostX86 Include="$(ArtifactsBinDir)testhost.x86\$(Configuration)\net462\win-x86\testhost.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net462\win-x86\testhost.x86.exe.config;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net47\win-x86\testhost.net47.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net47\win-x86\testhost.net47.x86.exe.config;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net471\win-x86\testhost.net471.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net471\win-x86\testhost.net471.x86.exe.config;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net472\win-x86\testhost.net472.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net472\win-x86\testhost.net472.x86.exe.config;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net48\win-x86\testhost.net48.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net48\win-x86\testhost.net48.x86.exe.config;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net481\win-x86\testhost.net481.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net481\win-x86\testhost.net481.x86.exe.config"> + contentFiles\any\net10.0\TestHostNetFramework\%(Filename)%(Extension) + None + true + false + + <_CliNetFrameworkTestHostArm64 Include="$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net462\win10-arm64\testhost.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net462\win10-arm64\testhost.arm64.exe.config;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net47\win10-arm64\testhost.net47.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net47\win10-arm64\testhost.net47.arm64.exe.config;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net471\win10-arm64\testhost.net471.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net471\win10-arm64\testhost.net471.arm64.exe.config;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net472\win10-arm64\testhost.net472.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net472\win10-arm64\testhost.net472.arm64.exe.config;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net48\win10-arm64\testhost.net48.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net48\win10-arm64\testhost.net48.arm64.exe.config;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net481\win10-arm64\testhost.net481.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net481\win10-arm64\testhost.net481.arm64.exe.config"> + contentFiles\any\net10.0\TestHostNetFramework\%(Filename)%(Extension) + None + true + false + + <_CliNetFrameworkDataCollector Include="$(ArtifactsBinDir)datacollector\$(Configuration)\net48\win7-x64\datacollector.exe;$(ArtifactsBinDir)datacollector\$(Configuration)\net48\win7-x64\datacollector.exe.config;$(ArtifactsBinDir)datacollector.arm64\$(Configuration)\net48\win10-arm64\datacollector.arm64.exe;$(ArtifactsBinDir)datacollector.arm64\$(Configuration)\net48\win10-arm64\datacollector.arm64.exe.config"> + contentFiles\any\net10.0\TestHostNetFramework\%(Filename)%(Extension) + None + true + false + + + + + diff --git a/src/testhost/testhost.csproj b/src/testhost/testhost.csproj index f38a65dc9c..e6207917e8 100644 --- a/src/testhost/testhost.csproj +++ b/src/testhost/testhost.csproj @@ -26,6 +26,15 @@ true + + + true + + win7-x64 false diff --git a/src/vstest.console/vstest.console.csproj b/src/vstest.console/vstest.console.csproj index b3229692e5..ecb449be1e 100644 --- a/src/vstest.console/vstest.console.csproj +++ b/src/vstest.console/vstest.console.csproj @@ -38,6 +38,13 @@ true + + + + true + From a883c68ebcbc9c42a381357d8faa304ca0175ad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Wed, 8 Jul 2026 18:11:32 +0200 Subject: [PATCH 33/87] Fix playground running 0 tests (#16238) The playground discovers tests but runs none of them. The run completes with 0 results and vstest.console logs "Could not find test executor with URI 'executor://mstestadapter/v2'", while discovery finds all 14 tests. The cause is the playground PostBuild copy, not the product. It copies the whole Microsoft.TestPlatform.TestHostProvider output folder into vstest.console\...\Extensions, which brings the provider's full dependency closure with it, including Common.dll. Common.dll contains SerialTestRunDecorator, an internal ITestExecutor with no parameterless constructor. vstest.console scans Extensions for adapters, discovers the decorator as an executor, and instantiating it throws MissingMethodException, which tears down the whole executor manager. No executor is registered after that, so nothing runs. Discovery uses the discoverer path, so it kept working and hid the failure. Copy only Microsoft.TestPlatform.TestHostRuntimeProvider* into Extensions, the same way the loggers copy only their own dll. The provider dependencies still resolve from the netfx root at runtime. This is a playground layout problem only. The shipped Microsoft.TestPlatform package never routes Common.dll into Extensions, so dotnet test and VS are not affected. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../TestPlatform.Playground.csproj | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/playground/TestPlatform.Playground/TestPlatform.Playground.csproj b/playground/TestPlatform.Playground/TestPlatform.Playground.csproj index 7259dc0728..e3b1c32724 100644 --- a/playground/TestPlatform.Playground/TestPlatform.Playground.csproj +++ b/playground/TestPlatform.Playground/TestPlatform.Playground.csproj @@ -65,7 +65,13 @@ - + + @@ -95,7 +101,8 @@ - + + From 9089918856d1ade13256bb261fbd1d198455ebc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Wed, 8 Jul 2026 18:17:14 +0200 Subject: [PATCH 34/87] Remove the experimental test session feature (#16231) * Remove the experimental test session feature StartTestSession/StopTestSession shipped in the translation layer marked [Obsolete("This API is not final yet and is subject to changes.")]. It was never finalized, Visual Studio and dotnet test don't use it, and the only external consumers I could find are two abandoned repositories. It carries its own wire messages, ObjectModel payloads and events, a process-wide TestSessionPool static, and telemetry constants that exist only to support it. Remove the feature end to end: - the StartTestSession/StopTestSession lifecycle across vstest.console, the translation layer, the design mode client, and the engine - ITestSession/ITestSessionAsync/ITestSessionEventsHandler and the Start/StopTestSessionCompleteEventArgs types - TestSessionPool and ProxyTestSessionManager - the StartTestSession*/StopTestSession* MessageType constants and their serialization payloads - the session telemetry constants TestSessionInfo stays. Every public RunTests/DiscoverTests overload keeps its testSessionInfo parameter, so this is not a binary break; those overloads already ran without a session when the parameter was null, which is now the only behavior. Removed public API is recorded in the PublicAPI.Unshipped.txt files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Restore UTF-8 BOM on edited xlf files The test-session removal stripped the BOM from these 26 localization files; the other 221 xlf files in the repo keep it. Put it back to match the convention and avoid encoding churn. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- playground/TestPlatform.Playground/Program.cs | 41 +- .../DesignMode/DesignModeClient.cs | 83 -- .../PublicAPI/PublicAPI.Unshipped.txt | 2 + .../RequestHelper/ITestRequestManager.cs | 27 - .../TestPlatform.cs | 33 - .../InProcessTestSessionEventsHandler.cs | 54 -- .../TestSession/TestSessionEventsHandler.cs | 70 -- .../IProxyTestSessionManager.cs | 34 - .../Engine/ClientProtocol/ITestEngine.cs | 21 - .../PublicAPI/PublicAPI.Unshipped.txt | 4 + .../Telemetry/TelemetryDataConstants.cs | 17 - .../Messages/MessageType.cs | 20 - .../PublicAPI/PublicAPI.Unshipped.txt | 4 + .../TestPlatformJsonContext.cs | 7 - .../PublicAPI/PublicAPI.Unshipped.txt | 8 + .../PublicAPI/net/PublicAPI.Unshipped.txt | 8 + .../Interfaces/ITestPlatformEventSource.cs | 40 - .../Tracing/TestPlatformEventSource.cs | 56 -- .../TestPlatformInstrumentationEvents.cs | 40 - .../Client/ProxyDiscoveryManager.cs | 62 +- .../Client/ProxyExecutionManager.cs | 83 +- .../PublicAPI/PublicAPI.Unshipped.txt | 14 + .../TestEngine.cs | 211 +---- .../TestSession/ProxyTestSessionManager.cs | 476 ----------- .../TestSession/TestSessionPool.cs | 216 ----- .../StartTestSessionCompleteEventArgs.cs | 27 - .../StopTestSessionCompleteEventArgs.cs | 49 -- .../Client/Interfaces/ITestPlatform.cs | 20 - .../Interfaces/ITestSessionEventsHandler.cs | 24 - .../Payloads/StartTestSessionAckPayload.cs | 20 - .../Payloads/StartTestSessionPayload.cs | 45 -- .../Payloads/StopTestSessionAckPayload.cs | 20 - .../Client/Payloads/StopTestSessionPayload.cs | 26 - .../Client/StartTestSessionCriteria.cs | 34 - .../PublicAPI/PublicAPI.Unshipped.txt | 53 ++ .../Interfaces/ITestSession.cs | 292 ------- .../Interfaces/ITestSessionAsync.cs | 291 ------- .../ITranslationLayerRequestSender.cs | 29 - .../ITranslationLayerRequestSenderAsync.cs | 28 - .../Interfaces/IVsTestConsoleWrapper.cs | 80 -- .../Interfaces/IVsTestConsoleWrapperAsync.cs | 72 +- .../PublicAPI/PublicAPI.Unshipped.txt | 108 ++- .../Resources/Resources.Designer.cs | 20 +- .../Resources/Resources.resx | 8 +- .../Resources/xlf/Resources.cs.xlf | 10 - .../Resources/xlf/Resources.de.xlf | 10 - .../Resources/xlf/Resources.es.xlf | 10 - .../Resources/xlf/Resources.fr.xlf | 10 - .../Resources/xlf/Resources.it.xlf | 10 - .../Resources/xlf/Resources.ja.xlf | 10 - .../Resources/xlf/Resources.ko.xlf | 10 - .../Resources/xlf/Resources.pl.xlf | 10 - .../Resources/xlf/Resources.pt-BR.xlf | 10 - .../Resources/xlf/Resources.ru.xlf | 10 - .../Resources/xlf/Resources.tr.xlf | 10 - .../Resources/xlf/Resources.zh-Hans.xlf | 10 - .../Resources/xlf/Resources.zh-Hant.xlf | 10 - .../TestSession.cs | 654 --------------- .../VsTestConsoleRequestSender.cs | 380 --------- .../VsTestConsoleWrapper.cs | 173 ---- .../Internal/NullWarningLogger.cs | 14 - .../Resources/Resources.Designer.cs | 20 +- src/vstest.console/Resources/Resources.resx | 8 +- .../Resources/xlf/Resources.cs.xlf | 10 - .../Resources/xlf/Resources.de.xlf | 10 - .../Resources/xlf/Resources.es.xlf | 10 - .../Resources/xlf/Resources.fr.xlf | 10 - .../Resources/xlf/Resources.it.xlf | 10 - .../Resources/xlf/Resources.ja.xlf | 10 - .../Resources/xlf/Resources.ko.xlf | 10 - .../Resources/xlf/Resources.pl.xlf | 10 - .../Resources/xlf/Resources.pt-BR.xlf | 10 - .../Resources/xlf/Resources.ru.xlf | 10 - .../Resources/xlf/Resources.tr.xlf | 10 - .../Resources/xlf/Resources.zh-Hans.xlf | 10 - .../Resources/xlf/Resources.zh-Hant.xlf | 10 - .../TestPlatformHelpers/TestRequestManager.cs | 153 ---- .../DesignMode/DesignModeClientTests.cs | 97 --- .../TestPlatformTests.cs | 161 ---- ...rtTestSessionCallbackSerializationTests.cs | 160 ---- .../StartTestSessionSerializationTests.cs | 176 ---- ...opTestSessionCallbackSerializationTests.cs | 165 ---- .../StopTestSessionSerializationTests.cs | 139 ---- .../Client/ProxyDiscoveryManagerTests.cs | 60 -- .../Client/ProxyExecutionManagerTests.cs | 61 -- .../Client/ProxyTestSessionManagerTests.cs | 660 --------------- .../TestEngineTests.cs | 462 ----------- .../TestSession/TestSessionPoolTests.cs | 135 ---- .../TestSessionTests.cs | 749 ------------------ .../VsTestConsoleRequestSenderTests.cs | 458 ----------- .../VsTestConsoleWrapperTests.cs | 136 +--- .../Fakes/FakeTestPlatformEventSource.cs | 40 - .../Fakes/FakeTestSessionEventsHandler.cs | 48 -- test/vstest.ProgrammerTests/Fakes/Fixture.cs | 3 - .../MultiTFMRunAndDiscovery.cs | 120 +-- .../TestRequestManagerTests.cs | 342 +------- 96 files changed, 218 insertions(+), 8493 deletions(-) delete mode 100644 src/Microsoft.TestPlatform.Client/TestSession/InProcessTestSessionEventsHandler.cs delete mode 100644 src/Microsoft.TestPlatform.Client/TestSession/TestSessionEventsHandler.cs delete mode 100644 src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IProxyTestSessionManager.cs delete mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/ProxyTestSessionManager.cs delete mode 100644 src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/TestSessionPool.cs delete mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Events/StartTestSessionCompleteEventArgs.cs delete mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Events/StopTestSessionCompleteEventArgs.cs delete mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestSessionEventsHandler.cs delete mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Payloads/StartTestSessionAckPayload.cs delete mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Payloads/StartTestSessionPayload.cs delete mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Payloads/StopTestSessionAckPayload.cs delete mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/Payloads/StopTestSessionPayload.cs delete mode 100644 src/Microsoft.TestPlatform.ObjectModel/Client/StartTestSessionCriteria.cs delete mode 100644 src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITestSession.cs delete mode 100644 src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITestSessionAsync.cs delete mode 100644 src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/TestSession.cs delete mode 100644 src/vstest.console/Internal/NullWarningLogger.cs delete mode 100644 test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/StartTestSessionCallbackSerializationTests.cs delete mode 100644 test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/StartTestSessionSerializationTests.cs delete mode 100644 test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/StopTestSessionCallbackSerializationTests.cs delete mode 100644 test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/Serialization/StopTestSessionSerializationTests.cs delete mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/Client/ProxyTestSessionManagerTests.cs delete mode 100644 test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestSession/TestSessionPoolTests.cs delete mode 100644 test/TranslationLayer.UnitTests/TestSessionTests.cs delete mode 100644 test/vstest.ProgrammerTests/Fakes/FakeTestSessionEventsHandler.cs diff --git a/playground/TestPlatform.Playground/Program.cs b/playground/TestPlatform.Playground/Program.cs index c41c50e17d..f868f011ee 100644 --- a/playground/TestPlatform.Playground/Program.cs +++ b/playground/TestPlatform.Playground/Program.cs @@ -136,26 +136,21 @@ static void Main() CollectMetrics = true, }; var r = new VsTestConsoleWrapper(console, consoleOptions); - var sessionHandler = new TestSessionHandler(); -#pragma warning disable CS0618 // Type or member is obsolete - //// TestSessions - // r.StartTestSession(sources, sourceSettings, sessionHandler); -#pragma warning restore CS0618 // Type or member is obsolete var discoveryHandler = new PlaygroundTestDiscoveryHandler(detailedOutput); var sw = Stopwatch.StartNew(); // Discovery - r.DiscoverTests(sources, sourceSettings, options, sessionHandler.TestSessionInfo, discoveryHandler); + r.DiscoverTests(sources, sourceSettings, options, testSessionInfo: null, discoveryHandler); var discoveryDuration = sw.ElapsedMilliseconds; Console.WriteLine($"Discovery done in {discoveryDuration} ms"); sw.Restart(); // Run with test cases and custom testhost launcher - //r.RunTestsWithCustomTestHost(discoveryHandler.TestCases, sourceSettings, options, sessionHandler.TestSessionInfo, new TestRunHandler(detailedOutput), new DebuggerTestHostLauncher()); + //r.RunTestsWithCustomTestHost(discoveryHandler.TestCases, sourceSettings, options, testSessionInfo: null, new TestRunHandler(detailedOutput), new DebuggerTestHostLauncher()); //// Run with test cases and without custom testhost launcher - r.RunTests(discoveryHandler.TestCases, sourceSettings, options, sessionHandler.TestSessionInfo, new TestRunHandler(detailedOutput)); + r.RunTests(discoveryHandler.TestCases, sourceSettings, options, testSessionInfo: null, new TestRunHandler(detailedOutput)); //// Run with sources and custom testhost launcher and debugging - //r.RunTestsWithCustomTestHost(sources, sourceSettings, options, sessionHandler.TestSessionInfo, new TestRunHandler(detailedOutput), new DebuggerTestHostLauncher()); + //r.RunTestsWithCustomTestHost(sources, sourceSettings, options, testSessionInfo: null, new TestRunHandler(detailedOutput), new DebuggerTestHostLauncher()); //// Run with sources - //r.RunTests(sources, sourceSettings, options, sessionHandler.TestSessionInfo, new TestRunHandler(detailedOutput)); + //r.RunTests(sources, sourceSettings, options, testSessionInfo: null, new TestRunHandler(detailedOutput)); var rd = sw.ElapsedMilliseconds; Console.WriteLine($"Discovery: {discoveryDuration} ms, Run: {rd} ms, Total: {discoveryDuration + rd} ms"); // Console.WriteLine($"Settings:\n{sourceSettings}"); @@ -314,29 +309,3 @@ public int LaunchTestHost(TestProcessStartInfo defaultTestHostStartInfo, Cancell } } } - -internal class TestSessionHandler : ITestSessionEventsHandler -{ - public TestSessionHandler() { } - public TestSessionInfo? TestSessionInfo { get; private set; } - - public void HandleLogMessage(TestMessageLevel level, string? message) - { - - } - - public void HandleRawMessage(string rawMessage) - { - - } - - public void HandleStartTestSessionComplete(StartTestSessionCompleteEventArgs? eventArgs) - { - TestSessionInfo = eventArgs?.TestSessionInfo; - } - - public void HandleStopTestSessionComplete(StopTestSessionCompleteEventArgs? eventArgs) - { - - } -} diff --git a/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeClient.cs b/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeClient.cs index f64f20374e..3da30b634d 100644 --- a/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeClient.cs +++ b/src/Microsoft.TestPlatform.Client/DesignMode/DesignModeClient.cs @@ -21,7 +21,6 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; @@ -189,20 +188,6 @@ private void ProcessRequests(ITestRequestManager testRequestManager) break; } - case MessageType.StartTestSession: - { - var testSessionPayload = _communicationManager.DeserializePayload(message); - StartTestSession(testSessionPayload, testRequestManager); - break; - } - - case MessageType.StopTestSession: - { - var testSessionPayload = _communicationManager.DeserializePayload(message); - StopTestSession(testSessionPayload, testRequestManager); - break; - } - case MessageType.StartDiscovery: { var discoveryPayload = _dataSerializer.DeserializePayload(message); @@ -591,74 +576,6 @@ void OnError(Exception? ex) } } - private void StartTestSession(StartTestSessionPayload? payload, ITestRequestManager requestManager) - { - Task.Run(() => - { - var eventsHandler = new TestSessionEventsHandler(_communicationManager); - - try - { - if (payload is null) - { - OnError(eventsHandler, null); - return; - } - - var customLauncher = payload.HasCustomHostLauncher - ? DesignModeTestHostLauncherFactory.GetCustomHostLauncherForTestRun(this, payload.IsDebuggingEnabled) - : null; - - requestManager.ResetOptions(); - requestManager.StartTestSession(payload, customLauncher, eventsHandler, _protocolConfig); - } - catch (Exception ex) - { - OnError(eventsHandler, ex); - } - }); - - static void OnError(TestSessionEventsHandler eventsHandler, Exception? ex) - { - EqtTrace.Error("DesignModeClient.StartTestSession: " + ex ?? "payload is null"); - - eventsHandler.HandleLogMessage(TestMessageLevel.Error, ex?.ToString()); - eventsHandler.HandleStartTestSessionComplete(new()); - } - } - - private void StopTestSession(StopTestSessionPayload? payload, ITestRequestManager requestManager) - { - Task.Run(() => - { - var eventsHandler = new TestSessionEventsHandler(_communicationManager); - - try - { - requestManager.ResetOptions(); - if (payload is null) - { - OnError(eventsHandler, null); - return; - } - - requestManager.StopTestSession(payload, eventsHandler, _protocolConfig); - } - catch (Exception ex) - { - OnError(eventsHandler, ex); - } - }); - - void OnError(TestSessionEventsHandler eventsHandler, Exception? ex) - { - EqtTrace.Error("DesignModeClient.StopTestSession: " + ex ?? "payload is null"); - - eventsHandler.HandleLogMessage(TestMessageLevel.Error, ex?.ToString()); - eventsHandler.HandleStopTestSessionComplete(new(payload?.TestSessionInfo)); - } - } - #region IDisposable Support private bool _isDisposed; // To detect redundant calls diff --git a/src/Microsoft.TestPlatform.Client/PublicAPI/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.Client/PublicAPI/PublicAPI.Unshipped.txt index 7dc5c58110..47992b554c 100644 --- a/src/Microsoft.TestPlatform.Client/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/Microsoft.TestPlatform.Client/PublicAPI/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +*REMOVED*Microsoft.VisualStudio.TestPlatform.Client.RequestHelper.ITestRequestManager.StopTestSession(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StopTestSessionPayload! payload, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestSessionEventsHandler! eventsHandler, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ProtocolConfig! protocolConfig) -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.Client.RequestHelper.ITestRequestManager.StartTestSession(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StartTestSessionPayload! payload, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces.ITestHostLauncher3? testHostLauncher, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestSessionEventsHandler! eventsHandler, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ProtocolConfig! protocolConfig) -> void diff --git a/src/Microsoft.TestPlatform.Client/RequestHelper/ITestRequestManager.cs b/src/Microsoft.TestPlatform.Client/RequestHelper/ITestRequestManager.cs index 758c63a6f0..3f840b55e0 100644 --- a/src/Microsoft.TestPlatform.Client/RequestHelper/ITestRequestManager.cs +++ b/src/Microsoft.TestPlatform.Client/RequestHelper/ITestRequestManager.cs @@ -8,7 +8,6 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads; namespace Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; @@ -74,32 +73,6 @@ void ProcessTestRunAttachments( ITestRunAttachmentsProcessingEventsHandler testRunAttachmentsProcessingEventsHandler, ProtocolConfig protocolConfig); - /// - /// Starts a test session. - /// - /// - /// The start test session payload. - /// The custom test host launcher. - /// The events handler. - /// Protocol related information. - void StartTestSession( - StartTestSessionPayload payload, - ITestHostLauncher3? testHostLauncher, - ITestSessionEventsHandler eventsHandler, - ProtocolConfig protocolConfig); - - /// - /// Stops a test session. - /// - /// - /// The stop test session payload. - /// The events handler. - /// Protocol related information. - void StopTestSession( - StopTestSessionPayload payload, - ITestSessionEventsHandler eventsHandler, - ProtocolConfig protocolConfig); - /// /// Cancel the current test run request. /// diff --git a/src/Microsoft.TestPlatform.Client/TestPlatform.cs b/src/Microsoft.TestPlatform.Client/TestPlatform.cs index 56a1dea9c2..e6249bc52a 100644 --- a/src/Microsoft.TestPlatform.Client/TestPlatform.cs +++ b/src/Microsoft.TestPlatform.Client/TestPlatform.cs @@ -135,39 +135,6 @@ private static bool GetSkipDefaultAdapters(TestPlatformOptions? options, string? return false; } - /// - public bool StartTestSession( - IRequestData requestData, - StartTestSessionCriteria testSessionCriteria, - ITestSessionEventsHandler eventsHandler, - Dictionary sourceToSourceDetailMap, - IWarningLogger warningLogger) - { - ValidateArg.NotNull(testSessionCriteria, nameof(testSessionCriteria)); - - RunConfiguration runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(testSessionCriteria.RunSettings); - TestAdapterLoadingStrategy strategy = runConfiguration.TestAdapterLoadingStrategy; - - AddExtensionAssemblies(testSessionCriteria.RunSettings, strategy); - - if (!runConfiguration.DesignMode) - { - return false; - } - - IProxyTestSessionManager? testSessionManager = _testEngine.GetTestSessionManager(requestData, testSessionCriteria, sourceToSourceDetailMap, warningLogger); - if (testSessionManager == null) - { - // The test session manager is null because the combination of runsettings and - // sources tells us we should run in-process (i.e. in vstest.console). Because - // of this no session will be created because there's no testhost to be launched. - // Expecting a subsequent call to execute tests with the same set of parameters. - return false; - } - - return testSessionManager.StartSession(eventsHandler, requestData); - } - private void PopulateExtensions(string? runSettings, IEnumerable sources) { RunConfiguration runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(runSettings); diff --git a/src/Microsoft.TestPlatform.Client/TestSession/InProcessTestSessionEventsHandler.cs b/src/Microsoft.TestPlatform.Client/TestSession/InProcessTestSessionEventsHandler.cs deleted file mode 100644 index 106a285bdd..0000000000 --- a/src/Microsoft.TestPlatform.Client/TestSession/InProcessTestSessionEventsHandler.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; - -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; - -namespace Microsoft.VisualStudio.TestPlatform.Client; - -internal class InProcessTestSessionEventsHandler : ITestSessionEventsHandler -{ - private readonly ITestSessionEventsHandler _testSessionEventsHandler; - - public EventHandler? StartTestSessionCompleteEventHandler { get; set; } - - public EventHandler? StopTestSessionCompleteEventHandler { get; set; } - - public InProcessTestSessionEventsHandler(ITestSessionEventsHandler testSessionEventsHandler) - { - _testSessionEventsHandler = testSessionEventsHandler; - } - - public void HandleLogMessage(TestMessageLevel level, string? message) - { - _testSessionEventsHandler.HandleLogMessage(level, message); - } - - public void HandleRawMessage(string rawMessage) - { - // No-op by design. - // - // For out-of-process vstest.console, raw messages are passed to the translation layer but - // they are never read and don't get passed to the actual events handler in TW. If they - // were (as it happens for in-process vstest.console since there is no more translation - // layer) a NotImplemented exception would be raised as per the time this of writing this - // note. - // - // Consider changing this logic in the future if TW changes the handling logic for raw - // messages. - } - - public void HandleStartTestSessionComplete(StartTestSessionCompleteEventArgs? eventArgs) - { - StartTestSessionCompleteEventHandler?.Invoke(this, eventArgs); - _testSessionEventsHandler.HandleStartTestSessionComplete(eventArgs); - } - - public void HandleStopTestSessionComplete(StopTestSessionCompleteEventArgs? eventArgs) - { - StopTestSessionCompleteEventHandler?.Invoke(this, eventArgs); - _testSessionEventsHandler.HandleStopTestSessionComplete(eventArgs); - } -} diff --git a/src/Microsoft.TestPlatform.Client/TestSession/TestSessionEventsHandler.cs b/src/Microsoft.TestPlatform.Client/TestSession/TestSessionEventsHandler.cs deleted file mode 100644 index 232a56a26d..0000000000 --- a/src/Microsoft.TestPlatform.Client/TestSession/TestSessionEventsHandler.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Interfaces; -using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; - -namespace Microsoft.VisualStudio.TestPlatform.Client; - -/// -/// Defines the way in which test session events should be handled. -/// -internal class TestSessionEventsHandler : ITestSessionEventsHandler -{ - private readonly ICommunicationManager _communicationManager; - - /// - /// Creates an instance of the current class. - /// - /// - /// - /// The communication manager used for passing messages around. - /// - public TestSessionEventsHandler(ICommunicationManager communicationManager) - { - _communicationManager = communicationManager; - } - - /// - public void HandleStartTestSessionComplete(StartTestSessionCompleteEventArgs? eventArgs) - { - var ackPayload = new StartTestSessionAckPayload - { - EventArgs = eventArgs - }; - - _communicationManager.SendMessage(MessageType.StartTestSessionCallback, ackPayload); - } - - /// - public void HandleStopTestSessionComplete(StopTestSessionCompleteEventArgs? eventArgs) - { - var ackPayload = new StopTestSessionAckPayload() - { - EventArgs = eventArgs - }; - - _communicationManager.SendMessage(MessageType.StopTestSessionCallback, ackPayload); - } - - /// - public void HandleLogMessage(TestMessageLevel level, string? message) - { - var messagePayload = new TestMessagePayload() - { - MessageLevel = level, - Message = message - }; - - _communicationManager.SendMessage(MessageType.TestMessage, messagePayload); - } - - /// - public void HandleRawMessage(string rawMessage) - { - // No-op. - } -} diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IProxyTestSessionManager.cs b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IProxyTestSessionManager.cs deleted file mode 100644 index 3a2acca8ac..0000000000 --- a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/IProxyTestSessionManager.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; - -namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; - -/// -/// Orchestrates test session related functionality for the engine communicating with the -/// client. -/// -public interface IProxyTestSessionManager -{ - /// - /// Starts the test session based on the test session criteria. - /// - /// - /// - /// Event handler for handling events fired during test session management operations. - /// - /// The request data. - /// - /// True if the operation succeeded, false otherwise. - bool StartSession(ITestSessionEventsHandler eventsHandler, IRequestData requestData); - - /// - /// Stops the test session. - /// - /// - /// The request data. - /// - /// True if the operation succeeded, false otherwise. - bool StopSession(IRequestData requestData); -} diff --git a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/ITestEngine.cs b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/ITestEngine.cs index d249244586..c44efc63ff 100644 --- a/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/ITestEngine.cs +++ b/src/Microsoft.TestPlatform.Common/Interfaces/Engine/ClientProtocol/ITestEngine.cs @@ -50,27 +50,6 @@ IProxyExecutionManager GetExecutionManager( IDictionary sourceToSourceDetailMap, IWarningLogger warningLogger); - /// - /// Fetches the TestSessionManager for this engine. This manager would provide all - /// functionality required for test session management. - /// - /// - /// - /// The request data for providing test session services and data. - /// - /// - /// Test session criteria of the current test session. - /// - /// Details of every source. - /// Logger of warnings. - /// - /// An IProxyTestSessionManager object that can manage test sessions. - IProxyTestSessionManager? GetTestSessionManager( - IRequestData requestData, - StartTestSessionCriteria testSessionCriteria, - IDictionary sourceToSourceDetailMap, - IWarningLogger warningLogger); - /// /// Fetches the extension manager for this engine. This manager would provide extensibility /// features that this engine supports. diff --git a/src/Microsoft.TestPlatform.Common/PublicAPI/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.Common/PublicAPI/PublicAPI.Unshipped.txt index c95ceedda6..0da9821a13 100644 --- a/src/Microsoft.TestPlatform.Common/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/Microsoft.TestPlatform.Common/PublicAPI/PublicAPI.Unshipped.txt @@ -1,2 +1,6 @@ #nullable enable Microsoft.VisualStudio.TestPlatform.Common.Interfaces.ITestDiscovererCapabilities.IsDirectoryBased.get -> bool +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyTestSessionManager +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyTestSessionManager.StartSession(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestSessionEventsHandler! eventsHandler, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData! requestData) -> bool +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyTestSessionManager.StopSession(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData! requestData) -> bool +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.ITestEngine.GetTestSessionManager(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData! requestData, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCriteria! testSessionCriteria, System.Collections.Generic.IDictionary! sourceToSourceDetailMap, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IWarningLogger! warningLogger) -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyTestSessionManager? diff --git a/src/Microsoft.TestPlatform.Common/Telemetry/TelemetryDataConstants.cs b/src/Microsoft.TestPlatform.Common/Telemetry/TelemetryDataConstants.cs index a864b79b3b..75b66b9e84 100644 --- a/src/Microsoft.TestPlatform.Common/Telemetry/TelemetryDataConstants.cs +++ b/src/Microsoft.TestPlatform.Common/Telemetry/TelemetryDataConstants.cs @@ -118,27 +118,10 @@ internal static class TelemetryDataConstants public static readonly string AttachmentsProcessingState = "VS.AttachmentsProcessing.State"; - // *********************Test Sessions**************************** - public static readonly string ParallelEnabledDuringStartTestSession = "VS.TestSession.ParallelEnabled"; - - public static readonly string TestSessionId = "VS.TestSession.Id"; - - public static readonly string TestSessionSpawnedTesthostCount = "VS.TestSession.SpawnedTesthostCount"; - - public static readonly string TestSessionTesthostSpawnTimeInSec = "VS.TestSession.TesthostSpawnTimeInSec"; - - public static readonly string TestSessionState = "VS.TestSession.State"; - - public static readonly string TestSessionTotalSessionTimeInSec = "VS.TestSession.TotalSessionTimeInSec"; - // **************Events Name ********************************** public static readonly string TestDiscoveryCompleteEvent = "vs/testplatform/testdiscoverysession"; public static readonly string TestExecutionCompleteEvent = "vs/testplatform/testrunsession"; public static readonly string TestAttachmentsProcessingCompleteEvent = "vs/testplatform/testattachmentsprocessingsession"; - - public static readonly string StartTestSessionCompleteEvent = "vs/testplatform/starttestsession"; - - public static readonly string StopTestSessionCompleteEvent = "vs/testplatform/stoptestsession"; } diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/MessageType.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/MessageType.cs index 29381d0f07..38a7fe3b17 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/MessageType.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Messages/MessageType.cs @@ -210,26 +210,6 @@ public static class MessageType /// public const string DataCollectionMessage = "DataCollection.SendMessage"; - /// - /// StartTestSession message. - /// - public const string StartTestSession = "TestSession.StartTestSession"; - - /// - /// StartTestSession callback message. - /// - public const string StartTestSessionCallback = "TestSession.StartTestSessionCallback"; - - /// - /// StopTestSession message. - /// - public const string StopTestSession = "TestSession.StopTestSession"; - - /// - /// StopTestSession callback message. - /// - public const string StopTestSessionCallback = "TestSession.StopTestSessionCallback"; - /// /// Event message type sent to datacollector process right after test host process has started. /// diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/PublicAPI/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.CommunicationUtilities/PublicAPI/PublicAPI.Unshipped.txt index 888f6721e5..f1e2ef68c7 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/PublicAPI/PublicAPI.Unshipped.txt @@ -3,3 +3,7 @@ const Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel.Mes ~static Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Resources.Resources.ConnectionTimeoutProcessDidNotStartErrorMessage.get -> string ~static Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Resources.Resources.ConnectionTimeoutProcessExitedErrorMessage.get -> string ~static Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Resources.Resources.ConnectionTimeoutWithDetailsErrorMessage.get -> string +*REMOVED*const Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel.MessageType.StartTestSession = "TestSession.StartTestSession" -> string! +*REMOVED*const Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel.MessageType.StartTestSessionCallback = "TestSession.StartTestSessionCallback" -> string! +*REMOVED*const Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel.MessageType.StopTestSession = "TestSession.StopTestSession" -> string! +*REMOVED*const Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.ObjectModel.MessageType.StopTestSessionCallback = "TestSession.StopTestSessionCallback" -> string! diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/TestPlatformJsonContext.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/TestPlatformJsonContext.cs index a3e7a03ec3..7537118724 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/TestPlatformJsonContext.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/TestPlatformJsonContext.cs @@ -13,7 +13,6 @@ using Microsoft.VisualStudio.TestPlatform.Common.DataCollection; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; @@ -67,8 +66,6 @@ namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; [JsonSerializable(typeof(TestRunCompletePayload))] [JsonSerializable(typeof(TestRunChangedEventArgs))] [JsonSerializable(typeof(TestRunStatsPayload))] -[JsonSerializable(typeof(StartTestSessionAckPayload))] -[JsonSerializable(typeof(StopTestSessionAckPayload))] [JsonSerializable(typeof(TestProcessStartInfo))] [JsonSerializable(typeof(EditorAttachDebuggerPayload))] [JsonSerializable(typeof(TelemetryEvent))] @@ -105,8 +102,6 @@ namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; [JsonSerializable(typeof(JsonDataSerializer.VersionedMessageForSerialization))] // --- Payload types SENT by VsTestConsoleRequestSender --- [JsonSerializable(typeof(TestRunRequestPayload))] -[JsonSerializable(typeof(StartTestSessionPayload))] -[JsonSerializable(typeof(StopTestSessionPayload))] [JsonSerializable(typeof(TestRunAttachmentsProcessingPayload))] [JsonSerializable(typeof(CustomHostLaunchAckPayload))] [JsonSerializable(typeof(EditorAttachDebuggerAckPayload))] @@ -118,8 +113,6 @@ namespace Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; [JsonSerializable(typeof(JsonDataSerializer.PayloadedMessage))] [JsonSerializable(typeof(JsonDataSerializer.PayloadedMessage))] [JsonSerializable(typeof(JsonDataSerializer.PayloadedMessage))] -[JsonSerializable(typeof(JsonDataSerializer.PayloadedMessage))] -[JsonSerializable(typeof(JsonDataSerializer.PayloadedMessage))] [JsonSerializable(typeof(JsonDataSerializer.PayloadedMessage))] [JsonSerializable(typeof(JsonDataSerializer.PayloadedMessage))] [JsonSerializable(typeof(JsonDataSerializer.PayloadedMessage))] diff --git a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/PublicAPI.Unshipped.txt index ab058de62d..d420711d63 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/PublicAPI.Unshipped.txt @@ -1 +1,9 @@ #nullable enable +*REMOVED*Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.Interfaces.ITestPlatformEventSource.StartTestSessionStart() -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.Interfaces.ITestPlatformEventSource.StartTestSessionStop() -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.Interfaces.ITestPlatformEventSource.StopTestSessionStart() -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.Interfaces.ITestPlatformEventSource.StopTestSessionStop() -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.Interfaces.ITestPlatformEventSource.TranslationLayerStartTestSessionStart() -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.Interfaces.ITestPlatformEventSource.TranslationLayerStartTestSessionStop() -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.Interfaces.ITestPlatformEventSource.TranslationLayerStopTestSessionStart() -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.Interfaces.ITestPlatformEventSource.TranslationLayerStopTestSessionStop() -> void diff --git a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net/PublicAPI.Unshipped.txt index ab058de62d..391341efec 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net/PublicAPI.Unshipped.txt +++ b/src/Microsoft.TestPlatform.CoreUtilities/PublicAPI/net/PublicAPI.Unshipped.txt @@ -1 +1,9 @@ #nullable enable +*REMOVED*Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.TestPlatformEventSource.StartTestSessionStart() -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.TestPlatformEventSource.StartTestSessionStop() -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.TestPlatformEventSource.StopTestSessionStart() -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.TestPlatformEventSource.StopTestSessionStop() -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.TestPlatformEventSource.TranslationLayerStartTestSessionStart() -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.TestPlatformEventSource.TranslationLayerStartTestSessionStop() -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.TestPlatformEventSource.TranslationLayerStopTestSessionStart() -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.CoreUtilities.Tracing.TestPlatformEventSource.TranslationLayerStopTestSessionStop() -> void diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Tracing/Interfaces/ITestPlatformEventSource.cs b/src/Microsoft.TestPlatform.CoreUtilities/Tracing/Interfaces/ITestPlatformEventSource.cs index 3953533dcf..79bd9aa827 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/Tracing/Interfaces/ITestPlatformEventSource.cs +++ b/src/Microsoft.TestPlatform.CoreUtilities/Tracing/Interfaces/ITestPlatformEventSource.cs @@ -218,44 +218,4 @@ public interface ITestPlatformEventSource /// Mark the completion of translation layer test run attachments processing request. /// void TranslationLayerTestRunAttachmentsProcessingStop(); - - /// - /// The start of the test session start request. - /// - void StartTestSessionStart(); - - /// - /// The end of the test session start request. - /// - void StartTestSessionStop(); - - /// - /// Mark the start of a translation layer start test session request. - /// - void TranslationLayerStartTestSessionStart(); - - /// - /// Mark the end of a translation layer start test session request. - /// - void TranslationLayerStartTestSessionStop(); - - /// - /// The start of the test session stop request. - /// - void StopTestSessionStart(); - - /// - /// The end of the test session stop request. - /// - void StopTestSessionStop(); - - /// - /// Mark the start of a translation layer stop test session request. - /// - void TranslationLayerStopTestSessionStart(); - - /// - /// Mark the end of a translation layer stop test session request. - /// - void TranslationLayerStopTestSessionStop(); } diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Tracing/TestPlatformEventSource.cs b/src/Microsoft.TestPlatform.CoreUtilities/Tracing/TestPlatformEventSource.cs index 42f1faa74c..8bebb6694a 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/Tracing/TestPlatformEventSource.cs +++ b/src/Microsoft.TestPlatform.CoreUtilities/Tracing/TestPlatformEventSource.cs @@ -280,60 +280,4 @@ public void TranslationLayerTestRunAttachmentsProcessingStop() { WriteEvent(TestPlatformInstrumentationEvents.TranslationLayerTestRunAttachmentsProcessingStopEventId); } - - /// - [Event(TestPlatformInstrumentationEvents.StartTestSessionStartEventId)] - public void StartTestSessionStart() - { - WriteEvent(TestPlatformInstrumentationEvents.StartTestSessionStartEventId); - } - - /// - [Event(TestPlatformInstrumentationEvents.StartTestSessionStopEventId)] - public void StartTestSessionStop() - { - WriteEvent(TestPlatformInstrumentationEvents.StartTestSessionStopEventId); - } - - /// - [Event(TestPlatformInstrumentationEvents.TranslationLayerStartTestSessionStartEventId)] - public void TranslationLayerStartTestSessionStart() - { - WriteEvent(TestPlatformInstrumentationEvents.TranslationLayerStartTestSessionStartEventId); - } - - /// - [Event(TestPlatformInstrumentationEvents.TranslationLayerStartTestSessionStopEventId)] - public void TranslationLayerStartTestSessionStop() - { - WriteEvent(TestPlatformInstrumentationEvents.TranslationLayerStartTestSessionStopEventId); - } - - /// - [Event(TestPlatformInstrumentationEvents.StopTestSessionStartEventId)] - public void StopTestSessionStart() - { - WriteEvent(TestPlatformInstrumentationEvents.StopTestSessionStartEventId); - } - - /// - [Event(TestPlatformInstrumentationEvents.StopTestSessionStopEventId)] - public void StopTestSessionStop() - { - WriteEvent(TestPlatformInstrumentationEvents.StopTestSessionStopEventId); - } - - /// - [Event(TestPlatformInstrumentationEvents.TranslationLayerStopTestSessionStartEventId)] - public void TranslationLayerStopTestSessionStart() - { - WriteEvent(TestPlatformInstrumentationEvents.TranslationLayerStopTestSessionStartEventId); - } - - /// - [Event(TestPlatformInstrumentationEvents.TranslationLayerStopTestSessionStopEventId)] - public void TranslationLayerStopTestSessionStop() - { - WriteEvent(TestPlatformInstrumentationEvents.TranslationLayerStopTestSessionStopEventId); - } } diff --git a/src/Microsoft.TestPlatform.CoreUtilities/Tracing/TestPlatformInstrumentationEvents.cs b/src/Microsoft.TestPlatform.CoreUtilities/Tracing/TestPlatformInstrumentationEvents.cs index 13073697aa..f06cf50537 100644 --- a/src/Microsoft.TestPlatform.CoreUtilities/Tracing/TestPlatformInstrumentationEvents.cs +++ b/src/Microsoft.TestPlatform.CoreUtilities/Tracing/TestPlatformInstrumentationEvents.cs @@ -187,44 +187,4 @@ internal class TestPlatformInstrumentationEvents /// Events fired on session attachments processing complete in translation layer. /// public const int TranslationLayerTestRunAttachmentsProcessingStopEventId = 0x45; - - /// - /// The start test session start event id. - /// - public const int StartTestSessionStartEventId = 0x46; - - /// - /// The start test session stop event id. - /// - public const int StartTestSessionStopEventId = 0x47; - - /// - /// The translation layer start test session start event id. - /// - public const int TranslationLayerStartTestSessionStartEventId = 0x48; - - /// - /// The translation layer start test session stop event id. - /// - public const int TranslationLayerStartTestSessionStopEventId = 0x49; - - /// - /// The stop test session start event id. - /// - public const int StopTestSessionStartEventId = 0x4A; - - /// - /// The stop test session stop event id. - /// - public const int StopTestSessionStopEventId = 0x4B; - - /// - /// The translation layer stop test session start event id. - /// - public const int TranslationLayerStopTestSessionStartEventId = 0x4C; - - /// - /// The translation layer stop test session stop event id. - /// - public const int TranslationLayerStopTestSessionStopEventId = 0x4D; } diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyDiscoveryManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyDiscoveryManager.cs index bfb5dace93..878b131826 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyDiscoveryManager.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyDiscoveryManager.cs @@ -27,48 +27,17 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; /// public class ProxyDiscoveryManager : IProxyDiscoveryManager, IBaseProxy, ITestDiscoveryEventsHandler2 { - private readonly TestSessionInfo? _testSessionInfo; - private readonly Func? _proxyOperationManagerCreator; - private readonly TestSessionPool? _testSessionPool; private readonly DiscoveryDataAggregator _discoveryDataAggregator; private readonly IFileHelper _fileHelper; private readonly IDataSerializer _dataSerializer; - private ITestRuntimeProvider? _testHostManager; + private readonly ITestRuntimeProvider? _testHostManager; private bool _isCommunicationEstablished; - private ProxyOperationManager? _proxyOperationManager; + private readonly ProxyOperationManager? _proxyOperationManager; private ITestDiscoveryEventsHandler2? _baseTestDiscoveryEventsHandler; private bool _skipDefaultAdapters; private string? _previousSource; - /// - /// Initializes a new instance of the class. - /// - /// - /// The test session info. - /// The proxy operation manager creator. - public ProxyDiscoveryManager( - TestSessionInfo testSessionInfo, - Func proxyOperationManagerCreator) - : this(testSessionInfo, proxyOperationManagerCreator, new()) - { - } - - internal ProxyDiscoveryManager( - TestSessionInfo testSessionInfo, - Func proxyOperationManagerCreator, - DiscoveryDataAggregator discoveryDataAggregator, - TestSessionPool? testSessionPool = null) - { - // Filling in test session info and proxy information. - _testSessionInfo = testSessionInfo; - _proxyOperationManagerCreator = proxyOperationManagerCreator; - _testSessionPool = testSessionPool; - _discoveryDataAggregator = discoveryDataAggregator; - _dataSerializer = JsonDataSerializer.Instance; - _fileHelper = new FileHelper(); - } - /// /// Initializes a new instance of the class. /// @@ -134,13 +103,7 @@ public void InitializeDiscovery(DiscoveryCriteria discoveryCriteria, ITestDiscov // it's built once. var discoverySources = discoveryCriteria.Sources.ToArray(); - if (_proxyOperationManager == null) - { - TPDebug.Assert(_proxyOperationManagerCreator is not null, "_proxyOperationManagerCreator is null"); - // Passing only first because that is how the testhost pool is keyed. - _proxyOperationManager = _proxyOperationManagerCreator(discoverySources[0], this); - _testHostManager = _proxyOperationManager.TestHostManager; - } + TPDebug.Assert(_proxyOperationManager is not null, "ProxyOperationManager is null."); _baseTestDiscoveryEventsHandler = eventHandler; @@ -276,23 +239,8 @@ public void Close() return; } - // When no test session is being used, we don't share the testhost - // between test discovery and test run. The testhost is closed upon - // successfully completing the operation it was spawned for. - // - // In contrast, the new workflow (using test sessions) means we should keep - // the testhost alive until explicitly closed by the test session owner, but - // only if the testhost is part of a test session (i.e. the proxy operation manager - // id is valid), since there is the distinct possibility of test session criteria - // changing between spawn and discovery/run, causing a new proxy operation manager - // to be spawned on demand instead of dequeuing an incompatible proxy from the pool. - if (_testSessionInfo == null || _proxyOperationManager.Id < 0) - { - _proxyOperationManager.Close(); - return; - } - - (_testSessionPool ?? TestSessionPool.Instance).ReturnProxy(_testSessionInfo, _proxyOperationManager.Id); + // The testhost is closed upon successfully completing the operation it was spawned for. + _proxyOperationManager.Close(); } /// diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyExecutionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyExecutionManager.cs index 5939e47852..5bdc38f001 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyExecutionManager.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/ProxyExecutionManager.cs @@ -32,17 +32,13 @@ namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; /// internal class ProxyExecutionManager : IProxyExecutionManager, IBaseProxy, IInternalTestRunEventsHandler { - private readonly TestSessionInfo? _testSessionInfo; - private readonly Func? _proxyOperationManagerCreator; - private readonly TestSessionPool? _testSessionPool; private readonly IFileHelper _fileHelper; private readonly IDataSerializer _dataSerializer; - private readonly bool _debugEnabledForTestSession; private List? _testSources; - private ITestRuntimeProvider? _testHostManager; + private readonly ITestRuntimeProvider? _testHostManager; private bool _isCommunicationEstablished; - private ProxyOperationManager? _proxyOperationManager; + private readonly ProxyOperationManager? _proxyOperationManager; private IInternalTestRunEventsHandler? _baseTestRunEventsHandler; private bool _skipDefaultAdapters; @@ -65,38 +61,6 @@ public CancellationTokenSource CancellationTokenSource _proxyOperationManager.CancellationTokenSource = value; } } - /// - /// Initializes a new instance of the class. - /// - /// - /// The test session info. - /// The proxy operation manager creator. - /// - /// A flag indicating if debugging should be enabled or not. - /// - /// - /// The test session pool to return proxies to, or to use the shared - /// . - /// - public ProxyExecutionManager( - TestSessionInfo testSessionInfo, - Func proxyOperationManagerCreator, - bool debugEnabledForTestSession, - TestSessionPool? testSessionPool = null) - { - // Filling in test session info and proxy information. - _testSessionInfo = testSessionInfo; - _proxyOperationManagerCreator = proxyOperationManagerCreator; - _testSessionPool = testSessionPool; - - // This should be set to enable debugging when we have test session info available. - _debugEnabledForTestSession = debugEnabledForTestSession; - - _testHostManager = null; - _dataSerializer = JsonDataSerializer.Instance; - _fileHelper = new FileHelper(); - _isCommunicationEstablished = false; - } /// /// Initializes a new instance of the class. @@ -166,22 +130,7 @@ public virtual void Initialize(bool skipDefaultAdapters) public virtual void InitializeTestRun(TestRunCriteria testRunCriteria, IInternalTestRunEventsHandler eventHandler) { - if (_proxyOperationManager == null) - { - // In case we have an active test session, we always prefer the already - // created proxies instead of the ones that need to be created on the spot. - var sources = testRunCriteria.HasSpecificTests - ? TestSourcesUtility.GetSources(testRunCriteria.Tests) - : testRunCriteria.Sources; - - TPDebug.Assert(_proxyOperationManagerCreator is not null, "_proxyOperationManagerCreator is null"); - TPDebug.Assert(sources is not null, "sources is null"); - _proxyOperationManager = _proxyOperationManagerCreator( - sources.First(), - this); - - _testHostManager = _proxyOperationManager.TestHostManager; - } + TPDebug.Assert(_proxyOperationManager is not null, "_proxyOperationManager is null"); _baseTestRunEventsHandler = eventHandler; try @@ -262,11 +211,8 @@ public virtual int StartTestRun(TestRunCriteria testRunCriteria, IInternalTestRu areTestCaseLevelEventsRequired: false, hasTestRun: true, // Debugging should happen if there's a custom test host launcher present - // and is in debugging mode, or if the debugging is enabled in case the - // test session info is present. - isDebug: - (testRunCriteria.TestHostLauncher != null && testRunCriteria.TestHostLauncher.IsDebug) - || _debugEnabledForTestSession, + // and is in debugging mode. + isDebug: testRunCriteria.TestHostLauncher != null && testRunCriteria.TestHostLauncher.IsDebug, testCaseFilter: testRunCriteria.TestCaseFilter, filterOptions: testRunCriteria.FilterOptions); @@ -382,23 +328,8 @@ public void Close() return; } - // When no test session is being used, we don't share the testhost - // between test discovery and test run. The testhost is closed upon - // successfully completing the operation it was spawned for. - // - // In contrast, the new workflow (using test sessions) means we should keep - // the testhost alive until explicitly closed by the test session owner, but - // only if the testhost is part of a test session (i.e. the proxy operation manager - // id is valid), since there is the distinct possibility of test session criteria - // changing between spawn and discovery/run, causing a new proxy operation manager - // to be spawned on demand instead of dequeuing an incompatible proxy from the pool. - if (_testSessionInfo == null || _proxyOperationManager.Id < 0) - { - _proxyOperationManager.Close(); - return; - } - - (_testSessionPool ?? TestSessionPool.Instance).ReturnProxy(_testSessionInfo, _proxyOperationManager.Id); + // The testhost is closed upon successfully completing the operation it was spawned for. + _proxyOperationManager.Close(); } /// diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/PublicAPI.Unshipped.txt index f4c6911c52..bcff29cb51 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/PublicAPI/PublicAPI.Unshipped.txt @@ -5,3 +5,17 @@ Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.IProxyManagerFactory. Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.MTP.MtpProxyManagerFactory static Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.MTP.MtpProxyManagerFactory.CreateDiscoveryManager() -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyDiscoveryManager! static Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.MTP.MtpProxyManagerFactory.CreateExecutionManager(Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.DataCollection.Interfaces.IProxyDataCollectionManager? dataCollectionManager) -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyExecutionManager! +*REMOVED*Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.ProxyTestSessionManager +*REMOVED*Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.ProxyTestSessionManager.ProxyTestSessionManager(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCriteria! criteria, int maxTesthostCount, System.Func! proxyCreator, System.Collections.Generic.List! runtimeProviders) -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestEngine.GetTestSessionManager(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData! requestData, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCriteria! testSessionCriteria, System.Collections.Generic.IDictionary! sourceToSourceDetailMap, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IWarningLogger! warningLogger) -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine.IProxyTestSessionManager? +*REMOVED*Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestSessionPool +*REMOVED*static Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestSessionPool.Instance.get -> Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestSessionPool! +*REMOVED*virtual Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.ProxyTestSessionManager.DequeueProxy(string! source, string? runSettings) -> Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.ProxyOperationManager! +*REMOVED*virtual Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.ProxyTestSessionManager.EnqueueProxy(int proxyId) -> bool +*REMOVED*virtual Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.ProxyTestSessionManager.StartSession(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestSessionEventsHandler! eventsHandler, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData! requestData) -> bool +*REMOVED*virtual Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.ProxyTestSessionManager.StopSession(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData! requestData) -> bool +*REMOVED*Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.ProxyDiscoveryManager.ProxyDiscoveryManager(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestSessionInfo! testSessionInfo, System.Func! proxyOperationManagerCreator) -> void +*REMOVED*virtual Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestSessionPool.AddSession(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestSessionInfo! testSessionInfo, Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.ProxyTestSessionManager! proxyManager) -> bool +*REMOVED*virtual Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestSessionPool.KillSession(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestSessionInfo! testSessionInfo, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData! requestData) -> bool +*REMOVED*virtual Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestSessionPool.ReturnProxy(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestSessionInfo! testSessionInfo, int proxyId) -> bool +*REMOVED*virtual Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.TestSessionPool.TryTakeProxy(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestSessionInfo! testSessionInfo, string! source, string? runSettings, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData! requestData) -> Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.ProxyOperationManager? diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/TestEngine.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/TestEngine.cs index 330ea6c43d..867aa03b59 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/TestEngine.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/TestEngine.cs @@ -36,7 +36,6 @@ public class TestEngine : ITestEngine private readonly ITestRuntimeProviderManager _testHostProviderManager; private readonly IProcessHelper _processHelper; private readonly IEnvironment _environment; - private readonly TestSessionPool? _testSessionPool; private ITestExtensionManager? _testExtensionManager; @@ -55,13 +54,11 @@ protected internal TestEngine( internal TestEngine( ITestRuntimeProviderManager testHostProviderManager, IProcessHelper processHelper, - IEnvironment environment, - TestSessionPool? testSessionPool = null) + IEnvironment environment) { _testHostProviderManager = testHostProviderManager; _processHelper = processHelper; _environment = environment; - _testSessionPool = testSessionPool; } #region ITestEngine implementation @@ -86,7 +83,6 @@ public IProxyDiscoveryManager GetDiscoveryManager( // Collecting IsParallel enabled. requestData.MetricsCollection.Add(TelemetryDataConstants.ParallelEnabledDuringDiscovery, isParallelRun ? "True" : "False"); - requestData.MetricsCollection.Add(TelemetryDataConstants.TestSessionId, discoveryCriteria.TestSessionInfo?.Id.ToString() ?? string.Empty); // Get testhost managers by configuration, and either use it for in-process run. or for single source run. List testHostManagers = GetTestRuntimeProvidersForUniqueConfigurations(discoveryCriteria.RunSettings!, sourceToSourceDetailMap, warningLogger, out ITestRuntimeProvider? testHostManager); @@ -142,60 +138,13 @@ public IProxyDiscoveryManager GetDiscoveryManager( ThrowExceptionIfTestHostManagerIsNull(hostManager, runtimeProviderInfo.RunSettings); TPDebug.Assert(hostManager is not null, "hostManager is null"); - // This function is used to either take a pre-existing proxy operation manager from - // the test pool or to create a new proxy operation manager on the spot. - Func - proxyOperationManagerCreator = ( - string source, - ProxyDiscoveryManager proxyDiscoveryManager) => - { - TPDebug.Assert(discoveryCriteria.TestSessionInfo is not null, "discoveryCriteria.TestSessionInfo is null"); - - // In case we have an active test session, we always prefer the already - // created proxies instead of the ones that need to be created on the spot. - var proxyOperationManager = (_testSessionPool ?? TestSessionPool.Instance).TryTakeProxy( - discoveryCriteria.TestSessionInfo, - source, - runtimeProviderInfo.RunSettings, - requestData); - - if (proxyOperationManager == null) - { - // If the proxy creation process based on test session info failed, then - // we'll proceed with the normal creation process as if no test session - // info was passed in in the first place. - // - // WARNING: This should not normally happen and it raises questions - // regarding the test session pool operation and consistency. - EqtTrace.Warning("ProxyDiscoveryManager creation with test session failed."); - - proxyOperationManager = new ProxyOperationManager( - requestData, - new TestRequestSender(requestData.ProtocolConfig!, hostManager), - hostManager, - // There is always at least one, and all of them have the same framework and architecture. - runtimeProviderInfo.SourceDetails[0].Framework, - proxyDiscoveryManager); - } - - return proxyOperationManager; - }; - - // In case we have an active test session, we always prefer the already - // created proxies instead of the ones that need to be created on the spot. - return (discoveryCriteria.TestSessionInfo != null) - ? new ProxyDiscoveryManager( - discoveryCriteria.TestSessionInfo, - proxyOperationManagerCreator, - discoveryDataAggregator, - _testSessionPool) - : new ProxyDiscoveryManager( - requestData, - new TestRequestSender(requestData.ProtocolConfig!, hostManager), - hostManager, - // There is always at least one, and all of them have the same framework and architecture. - runtimeProviderInfo.SourceDetails[0].Framework, - discoveryDataAggregator); + return new ProxyDiscoveryManager( + requestData, + new TestRequestSender(requestData.ProtocolConfig!, hostManager), + hostManager, + // There is always at least one, and all of them have the same framework and architecture. + runtimeProviderInfo.SourceDetails[0].Framework, + discoveryDataAggregator); }; return new ParallelProxyDiscoveryManager(requestData, proxyDiscoveryManagerCreator, discoveryDataAggregator, parallelLevel, testHostManagers); @@ -222,7 +171,6 @@ public IProxyExecutionManager GetExecutionManager( // Collecting IsParallel enabled. requestData.MetricsCollection.Add(TelemetryDataConstants.ParallelEnabledDuringExecution, isParallelRun ? "True" : "False"); - requestData.MetricsCollection.Add(TelemetryDataConstants.TestSessionId, testRunCriteria.TestSessionInfo?.Id.ToString() ?? string.Empty); var isDataCollectorEnabled = XmlRunSettingsUtilities.IsDataCollectionEnabled(testRunCriteria.TestRunSettings); var isInProcDataCollectorEnabled = XmlRunSettingsUtilities.IsInProcDataCollectionEnabled(testRunCriteria.TestRunSettings); @@ -308,54 +256,6 @@ internal IProxyExecutionManager CreateNonParallelExecutionManager(IRequestData r var requestSender = new TestRequestSender(requestData.ProtocolConfig!, hostManager); - if (testRunCriteria.TestSessionInfo != null) - { - // This function is used to either take a pre-existing proxy operation manager from - // the test pool or to create a new proxy operation manager on the spot. - Func - proxyOperationManagerCreator = ( - string source, - ProxyExecutionManager proxyExecutionManager) => - { - var proxyOperationManager = (_testSessionPool ?? TestSessionPool.Instance).TryTakeProxy( - testRunCriteria.TestSessionInfo, - source, - runtimeProviderInfo.RunSettings, - requestData); - - if (proxyOperationManager == null) - { - // If the proxy creation process based on test session info failed, then - // we'll proceed with the normal creation process as if no test session - // info was passed in in the first place. - // - // WARNING: This should not normally happen and it raises questions - // regarding the test session pool operation and consistency. - EqtTrace.Warning("ProxyExecutionManager creation with test session failed."); - - proxyOperationManager = new ProxyOperationManager( - requestData, - requestSender, - hostManager, - // There is always at least one, and all of them have the same framework and architecture. - runtimeProviderInfo.SourceDetails[0].Framework, - proxyExecutionManager); - } - - return proxyOperationManager; - }; - - // In case we have an active test session, data collection needs were - // already taken care of when first creating the session. As a consequence - // we always return this proxy instead of choosing between the vanilla - // execution proxy and the one with data collection enabled. - return new ProxyExecutionManager( - testRunCriteria.TestSessionInfo, - proxyOperationManagerCreator, - testRunCriteria.DebugEnabledForTestSession, - _testSessionPool); - } - return isDataCollectorEnabled ? new ProxyExecutionManagerWithDataCollection( requestData, @@ -375,101 +275,6 @@ internal IProxyExecutionManager CreateNonParallelExecutionManager(IRequestData r runtimeProviderInfo.SourceDetails[0].Framework!); } - /// - public IProxyTestSessionManager? GetTestSessionManager( - IRequestData requestData, - StartTestSessionCriteria testSessionCriteria, - IDictionary sourceToSourceDetailMap, - IWarningLogger warningLogger) - { - var parallelLevel = VerifyParallelSettingAndCalculateParallelLevel( - testSessionCriteria.Sources!.Count, - testSessionCriteria.RunSettings!); - - bool isParallelRun = parallelLevel > 1; - requestData.MetricsCollection.Add( - TelemetryDataConstants.ParallelEnabledDuringStartTestSession, - isParallelRun ? "True" : "False"); - - var isDataCollectorEnabled = XmlRunSettingsUtilities.IsDataCollectionEnabled(testSessionCriteria.RunSettings); - var isInProcDataCollectorEnabled = XmlRunSettingsUtilities.IsInProcDataCollectionEnabled(testSessionCriteria.RunSettings); - - List testRuntimeProviders = GetTestRuntimeProvidersForUniqueConfigurations(testSessionCriteria.RunSettings!, sourceToSourceDetailMap, warningLogger, out var _); - - if (ShouldRunInProcess( - testSessionCriteria.RunSettings!, - isParallelRun, - isDataCollectorEnabled || isInProcDataCollectorEnabled, - testRuntimeProviders)) - { - // In this case all tests will be run in the current process (vstest.console), so there is no - // testhost to pre-start. No session will be created, and the session info will be null. - return null; - } - - Func proxyCreator = testRuntimeProviderInfo => - { - var sources = testRuntimeProviderInfo.SourceDetails.Select(x => x.Source!).ToList(); - var hostManager = _testHostProviderManager.GetTestHostManagerByRunConfiguration(testRuntimeProviderInfo.RunSettings, sources); - ThrowExceptionIfTestHostManagerIsNull(hostManager, testRuntimeProviderInfo.RunSettings); - - hostManager!.Initialize(TestSessionMessageLogger.Instance, testRuntimeProviderInfo.RunSettings!); - if (testSessionCriteria.TestHostLauncher != null) - { - hostManager.SetCustomLauncher(testSessionCriteria.TestHostLauncher); - } - - var requestSender = new TestRequestSender(requestData.ProtocolConfig!, hostManager) - { - CloseConnectionOnOperationComplete = false - }; - - // TODO (copoiena): For now we don't support data collection alongside test - // sessions. - // - // The reason for this is that, in the case of Code Coverage for example, the - // data collector needs to pass some environment variables to the testhost process - // before the testhost process is started. This means that the data collector must - // be running when the testhost process is spawned, however the testhost process - // should be spawned during build, and it's problematic to have the data collector - // running during build because it must instrument the .dll files that don't exist - // yet. - return isDataCollectorEnabled - ? null - // ? new ProxyOperationManagerWithDataCollection( - // requestData, - // requestSender, - // hostManager, - // new ProxyDataCollectionManager( - // requestData, - // runsettingsXml, - // testSessionCriteria.Sources)) - // { - // CloseRequestSenderChannelOnProxyClose = true - // } - : new ProxyOperationManager( - requestData, - requestSender, - hostManager, - // There is always at least one, and all of them have the same framework and architecture. - testRuntimeProviderInfo.SourceDetails[0].Framework!) - { - IsTestSessionEnabled = true - }; - }; - - // TODO: This condition should be returning the maxParallel level to avoid pre-starting way too many testhosts, because maxParallel level, - // can be smaller than the number of sources to run. - var maxTesthostCount = isParallelRun ? testSessionCriteria.Sources.Count : 1; - - return new ProxyTestSessionManager(testSessionCriteria, maxTesthostCount, proxyCreator, testRuntimeProviders, _testSessionPool) - { - // Individual proxy setup failures are tolerated since SetupChannel may fail if the - // testhost it tries to start is not compatible with the test session feature. - DisposalPolicy = ProxyDisposalOnCreationFailPolicy.AllowProxySetupFailures - }; - } - private List GetTestRuntimeProvidersForUniqueConfigurations( string runSettings, IDictionary sourceToSourceDetailMap, diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/ProxyTestSessionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/ProxyTestSessionManager.cs deleted file mode 100644 index 54d1a445ca..0000000000 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/ProxyTestSessionManager.cs +++ /dev/null @@ -1,476 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.Linq; -using System.Threading.Tasks; - -using Microsoft.VisualStudio.TestPlatform.Common.Telemetry; -using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities; -using Microsoft.VisualStudio.TestPlatform.Utilities; - -using CrossPlatResources = Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Resources.Resources; - -namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine; - -internal enum ProxyDisposalOnCreationFailPolicy -{ - DisposeAllOnFailure, - AllowProxySetupFailures -} - -/// -/// Orchestrates test session operations for the engine communicating with the client. -/// -public class ProxyTestSessionManager : IProxyTestSessionManager -{ - private enum TestSessionState - { - Unknown, - Error, - Active, - Terminated - } - - private readonly object _lockObject = new(); - private readonly object _proxyOperationLockObject = new(); - private volatile bool _proxySetupFailed; - private readonly StartTestSessionCriteria _testSessionCriteria; - private readonly int _maxTesthostCount; - private TestSessionInfo? _testSessionInfo; - private readonly Func _proxyCreator; - private readonly List _runtimeProviders; - private readonly IList _proxyContainerList; - private readonly IDictionary _proxyMap; - private readonly Stopwatch _testSessionStopwatch; - private readonly Dictionary _sourceToRuntimeProviderInfoMap; - private readonly TestSessionPool? _testSessionPool; - private Dictionary _testSessionEnvironmentVariables = new(); - - internal ProxyDisposalOnCreationFailPolicy DisposalPolicy { get; set; } = ProxyDisposalOnCreationFailPolicy.DisposeAllOnFailure; - - private IDictionary TestSessionEnvironmentVariables - { - get - { - if (_testSessionEnvironmentVariables.Count == 0) - { - _testSessionEnvironmentVariables = InferRunSettingsHelper.GetEnvironmentVariables(_testSessionCriteria.RunSettings) - ?? _testSessionEnvironmentVariables; - } - - return _testSessionEnvironmentVariables; - } - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The test session criteria. - /// The testhost count. - /// The proxy creator. - /// Runtime providers. - public ProxyTestSessionManager( - StartTestSessionCriteria criteria, - int maxTesthostCount, - Func proxyCreator, - List runtimeProviders) - : this(criteria, maxTesthostCount, proxyCreator, runtimeProviders, testSessionPool: null) - { - } - - internal ProxyTestSessionManager( - StartTestSessionCriteria criteria, - int maxTesthostCount, - Func proxyCreator, - List runtimeProviders, - TestSessionPool? testSessionPool) - { - _testSessionCriteria = criteria; - _maxTesthostCount = maxTesthostCount; - _proxyCreator = proxyCreator; - _runtimeProviders = runtimeProviders; - _testSessionPool = testSessionPool; - _proxyContainerList = new List(); - _proxyMap = new Dictionary(); - _testSessionStopwatch = new Stopwatch(); - - // Get dictionary from source -> runtimeProviderInfo, that has the type of runtime provider to create for this - // source, and updated runsettings. - _sourceToRuntimeProviderInfoMap = _runtimeProviders - .SelectMany(runtimeProviderInfo => runtimeProviderInfo.SourceDetails.Select(detail => new KeyValuePair(detail.Source!, runtimeProviderInfo))) - .ToDictionary(pair => pair.Key, pair => pair.Value); - } - - // NOTE: The method is virtual for mocking purposes. - /// - public virtual bool StartSession(ITestSessionEventsHandler eventsHandler, IRequestData requestData) - { - lock (_lockObject) - { - if (_testSessionInfo != null) - { - return false; - } - _testSessionInfo = new TestSessionInfo(); - } - - var stopwatch = new Stopwatch(); - stopwatch.Start(); - - // TODO: Right now we either pre-create 1 testhost if parallel is disabled, or we pre-create as many - // testhosts as we have sources. In the future we will have a maxParallelLevel set to the actual parallel level - // (which might be lower than the number of sources) and we should do some kind of thinking here to figure out how to split the sources. - // To follow the way parallel execution and discovery is (supposed to be) working, there should be as many testhosts - // as the maxParallel level pre-started, and marked with the Shared, and configuration that they can run. - - // Create all the proxies in parallel, one task per proxy. - var taskList = new Task[_maxTesthostCount]; - for (int i = 0; i < taskList.Length; ++i) - { - // This is similar to what we do in ProxyExecutionManager, and ProxyDiscoveryManager, we split - // up the payload into multiple smaller pieces. Here it is one source per proxy. - TPDebug.Assert(_testSessionCriteria.Sources is not null, "_testSessionCriteria.Sources is null"); - var source = _testSessionCriteria.Sources[i]; - var sources = new List() { source }; - var runtimeProviderInfo = _sourceToRuntimeProviderInfoMap[source]; - - taskList[i] = Task.Factory.StartNew(() => - { - var proxySetupSucceeded = SetupRawProxy(sources, runtimeProviderInfo); - if (!proxySetupSucceeded) - { - // Set this only in the failed case, so we can check if any proxy failed to setup. - _proxySetupFailed = true; - } - }); - } - - // Wait for proxy creation to be over. - Task.WaitAll(taskList); - stopwatch.Stop(); - - // Collecting session metrics. - requestData?.MetricsCollection.Add( - TelemetryDataConstants.TestSessionId, - _testSessionInfo.Id); - requestData?.MetricsCollection.Add( - TelemetryDataConstants.TestSessionSpawnedTesthostCount, - _proxyContainerList.Count); - requestData?.MetricsCollection.Add( - TelemetryDataConstants.TestSessionTesthostSpawnTimeInSec, - stopwatch.Elapsed.TotalSeconds); - - // Dispose of all proxies if even one of them failed during setup. - // - // Update: With the introduction of the proxy creation fail disposal policy, we now support - // the scenario of individual proxy setup failures. What this means is that we don't mark - // the whole session as failed if a single proxy fails, but instead we'll reuse the spinned - // off testhosts when possible and create on-demand testhosts for the sources that we failed - // to create proxies for. - if (_proxySetupFailed) - { - if (DisposalPolicy == ProxyDisposalOnCreationFailPolicy.DisposeAllOnFailure - || _proxyContainerList.Count == 0) - { - requestData?.MetricsCollection.Add( - TelemetryDataConstants.TestSessionState, - TestSessionState.Error.ToString()); - DisposeProxies(); - return false; - } - - EqtTrace.Info($"ProxyTestSessionManager.StartSession: At least one proxy setup failed, but failures are tolerated by policy."); - } - - // Make the session available. - if (!(_testSessionPool ?? TestSessionPool.Instance).AddSession(_testSessionInfo, this)) - { - requestData?.MetricsCollection.Add( - TelemetryDataConstants.TestSessionState, - TestSessionState.Error.ToString()); - DisposeProxies(); - return false; - } - - requestData?.MetricsCollection.Add( - TelemetryDataConstants.TestSessionState, - TestSessionState.Active.ToString()); - - // This counts as the session start time. - _testSessionStopwatch.Start(); - - // Let the caller know the session has been created. - eventsHandler.HandleStartTestSessionComplete( - new() - { - TestSessionInfo = _testSessionInfo, - Metrics = requestData?.MetricsCollection.Metrics - }); - return true; - } - - // NOTE: The method is virtual for mocking purposes. - /// - public virtual bool StopSession(IRequestData requestData) - { - string testSessionId; - lock (_lockObject) - { - if (_testSessionInfo == null) - { - return false; - } - - testSessionId = _testSessionInfo.Id.ToString(); - _testSessionInfo = null; - } - - // Dispose of the pooled testhosts. - DisposeProxies(); - - // Compute session time. - _testSessionStopwatch.Stop(); - - // Collecting session metrics. - requestData?.MetricsCollection.Add( - TelemetryDataConstants.TestSessionId, - testSessionId); - requestData?.MetricsCollection.Add( - TelemetryDataConstants.TestSessionTotalSessionTimeInSec, - _testSessionStopwatch.Elapsed.TotalSeconds); - requestData?.MetricsCollection.Add( - TelemetryDataConstants.TestSessionState, - TestSessionState.Terminated.ToString()); - - return true; - } - - /// - /// Dequeues a proxy to be used either by discovery or execution. - /// - /// - /// The source to be associated to this proxy. - /// The run settings. - /// - /// The dequeued proxy. - public virtual ProxyOperationManager DequeueProxy(string source, string? runSettings) - { - ProxyOperationManagerContainer? proxyContainer; - - lock (_proxyOperationLockObject) - { - // No proxy available means the caller will have to create its own proxy. - if (!_proxyMap.TryGetValue(source, out int proxyIndex) - || !_proxyContainerList[proxyIndex].IsAvailable) - { - throw new InvalidOperationException(CrossPlatResources.NoAvailableProxyForDeque); - } - - // We must ensure the current run settings match the run settings from when the - // testhost was started. If not, throw an exception to force the caller to create - // its own proxy instead. - if (!CheckRunSettingsAreCompatible(runSettings)) - { - EqtTrace.Verbose($"ProxyTestSessionManager.DequeueProxy: A proxy exists, but the runsettings do not match. Skipping it. Incoming settings: {runSettings}, Settings on proxy: {_testSessionCriteria.RunSettings}"); - throw new InvalidOperationException(CrossPlatResources.NoProxyMatchesDescription); - } - - // Get the actual proxy. - proxyContainer = _proxyContainerList[proxyIndex]; - - // Mark the proxy as unavailable. - proxyContainer.IsAvailable = false; - } - - return proxyContainer.Proxy; - } - - /// - /// Enqueues a proxy back once discovery or executions is done with it. - /// - /// - /// The id of the proxy to be re-enqueued. - /// - /// True if the operation succeeded, false otherwise. - public virtual bool EnqueueProxy(int proxyId) - { - lock (_proxyOperationLockObject) - { - // Check if the proxy exists. - if (proxyId < 0 || proxyId >= _proxyContainerList.Count) - { - throw new ArgumentException( - string.Format( - CultureInfo.CurrentCulture, - CrossPlatResources.NoSuchProxyId, - proxyId)); - } - - // Get the actual proxy. - var proxyContainer = _proxyContainerList[proxyId]; - if (proxyContainer.IsAvailable) - { - throw new InvalidOperationException( - string.Format( - CultureInfo.CurrentCulture, - CrossPlatResources.ProxyIsAlreadyAvailable, - proxyId)); - } - - // Mark the proxy as available. - proxyContainer.IsAvailable = true; - } - - return true; - } - - private int EnqueueNewProxy( - IList sources, - ProxyOperationManagerContainer operationManagerContainer) - { - lock (_proxyOperationLockObject) - { - var index = _proxyContainerList.Count; - - // Add the proxy container to the proxy container list. - _proxyContainerList.Add(operationManagerContainer); - - foreach (var source in sources) - { - // Add the proxy index to the map. - _proxyMap.Add( - source, - index); - } - - return index; - } - } - - private bool SetupRawProxy( - IList sources, - TestRuntimeProviderInfo runtimeProviderInfo) - { - try - { - // Create and cache the proxy. - var operationManagerProxy = _proxyCreator(runtimeProviderInfo); - if (operationManagerProxy == null) - { - return false; - } - - // Initialize the proxy. - operationManagerProxy.Initialize(skipDefaultAdapters: false); - - // Start the test host associated to the proxy. - if (!operationManagerProxy.SetupChannel(sources, runtimeProviderInfo.RunSettings)) - { - return false; - } - - // Associate each source in the source list with this new proxy operation - // container. - var operationManagerContainer = new ProxyOperationManagerContainer( - operationManagerProxy, - available: true); - - operationManagerContainer.Proxy.Id = EnqueueNewProxy(sources, operationManagerContainer); - return true; - } - catch (Exception ex) - { - // Log & silently eat up the exception. It's a valid course of action to - // just forfeit proxy creation. This means that anyone wishing to get a - // proxy operation manager would have to create their own, on the spot, - // instead of getting one already created, and this case is handled - // gracefully already. - EqtTrace.Error( - "ProxyTestSessionManager.StartSession: Cannot create proxy. Error: {0}", - ex.ToString()); - } - - return false; - } - - private void DisposeProxies() - { - lock (_proxyOperationLockObject) - { - if (_proxyContainerList.Count == 0) - { - return; - } - - // Dispose of all the proxies in parallel, one task per proxy. - int i = 0; - var taskList = new Task[_proxyContainerList.Count]; - foreach (var proxyContainer in _proxyContainerList) - { - taskList[i++] = Task.Factory.StartNew(() => - // Initiate the end session handshake with the underlying testhost. - proxyContainer.Proxy.Close()); - } - - // Wait for proxy disposal to be over. - Task.WaitAll(taskList); - - _proxyContainerList.Clear(); - _proxyMap.Clear(); - } - } - - private bool CheckRunSettingsAreCompatible(string? requestRunSettings) - { - // Environment variable sets should be identical, otherwise it's not safe to reuse the - // already running testhosts. - var requestEnvironmentVariables = InferRunSettingsHelper.GetEnvironmentVariables(requestRunSettings); - if (requestEnvironmentVariables != null - && TestSessionEnvironmentVariables != null - && (requestEnvironmentVariables.Count != TestSessionEnvironmentVariables.Count - || requestEnvironmentVariables.Except(TestSessionEnvironmentVariables).Any())) - { - return false; - } - - // Data collection is not supported for test sessions yet. - return !XmlRunSettingsUtilities.IsDataCollectionEnabled(requestRunSettings); - } -} - -/// -/// Defines a container encapsulating the proxy and its corresponding state info. -/// -internal class ProxyOperationManagerContainer -{ - /// - /// Initializes a new instance of the class. - /// - /// - /// The proxy. - /// A flag indicating if the proxy is available to do work. - public ProxyOperationManagerContainer(ProxyOperationManager proxy, bool available) - { - Proxy = proxy; - IsAvailable = available; - } - - /// - /// Gets or sets the proxy. - /// - public ProxyOperationManager Proxy { get; set; } - - /// - /// Gets or sets a flag indicating if the proxy is available to do work. - /// - public bool IsAvailable { get; set; } -} diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/TestSessionPool.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/TestSessionPool.cs deleted file mode 100644 index ae563f9c1a..0000000000 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/TestSession/TestSessionPool.cs +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - -using Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client; -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; - -namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine; - -/// -/// Represents the test session pool. -/// -public class TestSessionPool -{ - private static readonly object InstanceLockObject = new(); - private static volatile TestSessionPool? s_instance; - - private readonly object _lockObject = new(); - private readonly Dictionary _sessionPool; - - /// - /// Initializes a new instance of the class. - /// - internal TestSessionPool() - { - _sessionPool = new Dictionary(); - } - - /// - /// Gets the test session pool instance. - /// Sets the test session pool instance for testing purposes only. - /// - /// - /// Thread-safe singleton pattern. - [AllowNull] - public static TestSessionPool Instance - { - get - { - if (s_instance == null) - { - lock (InstanceLockObject) - { - s_instance ??= new TestSessionPool(); - } - } - - return s_instance; - } - internal set - { - s_instance = value; - } - } - - /// - /// Adds a session to the pool. - /// - /// - /// The test session info object. - /// The proxy manager object. - /// - /// True if the operation succeeded, false otherwise. - public virtual bool AddSession( - TestSessionInfo testSessionInfo, - ProxyTestSessionManager proxyManager) - { - lock (_lockObject) - { - // Check if the session info already exists. - if (_sessionPool.ContainsKey(testSessionInfo)) - { - return false; - } - - // Adds an association between session info and proxy manager to the pool. - _sessionPool.Add(testSessionInfo, proxyManager); - return true; - } - } - - /// - /// Kills and removes a session from the pool. - /// - /// - /// The test session info object. - /// The request data. - /// - /// True if the operation succeeded, false otherwise. - public virtual bool KillSession(TestSessionInfo testSessionInfo, IRequestData requestData) - { - // TODO (copoiena): What happens if some request is running for the current session ? - // Should we stop the request as well ? Probably yes. - IProxyTestSessionManager? proxyManager; - - lock (_lockObject) - { - // Check if the session info exists. - if (!_sessionPool.TryGetValue(testSessionInfo, out var proxyManagerFromPool)) - { - return false; - } - - // Remove the session from the pool. - proxyManager = proxyManagerFromPool; - _sessionPool.Remove(testSessionInfo); - } - - // Kill the session. - return proxyManager.StopSession(requestData); - } - - /// - /// Gets a reference to the proxy object from the session pool. - /// - /// - /// The test session info object. - /// The source to be associated to this proxy. - /// The run settings. - /// The request data. - /// - /// The proxy object. - public virtual ProxyOperationManager? TryTakeProxy( - TestSessionInfo testSessionInfo, - string source, - string? runSettings, - IRequestData requestData) - { - ValidateArg.NotNull(requestData, nameof(requestData)); - - ProxyTestSessionManager? sessionManager; - lock (_lockObject) - { - if (!_sessionPool.TryGetValue(testSessionInfo, out sessionManager)) - { - return null; - } - } - - try - { - // Deque an actual proxy to do work. - var proxy = sessionManager.DequeueProxy(source, runSettings); - - // Make sure we use the per-request request data instead of the request data used when - // creating the test session. Otherwise we can end up having irrelevant telemetry for - // the current request being fulfilled or even duplicate telemetry which may cause an - // exception to be thrown. - proxy.RequestData = requestData; - - return proxy; - } - catch (InvalidOperationException ex) - { - // If we are unable to dequeue the proxy we just eat up the exception here as - // it is safe to proceed. - // - // WARNING: This should not normally happen and it raises questions regarding the - // test session pool operation and consistency. - EqtTrace.Warning("TestSessionPool.ReturnProxy failed: {0}", ex.ToString()); - } - - return null; - } - - /// - /// Returns the proxy object to the session pool. - /// - /// - /// The test session info object. - /// The proxy id to be returned. - /// - /// True if the operation succeeded, false otherwise. - public virtual bool ReturnProxy(TestSessionInfo testSessionInfo, int proxyId) - { - ProxyTestSessionManager? sessionManager; - lock (_lockObject) - { - if (!_sessionPool.TryGetValue(testSessionInfo, out sessionManager)) - { - return false; - } - } - - try - { - // Try re-enqueueing the specified proxy. - return sessionManager.EnqueueProxy(proxyId); - } - catch (Exception ex) - { - // If we are unable to re-enqueue the proxy, we just eat up the exception here as - // it is safe to proceed. Returning a proxy is a fire-and-forget kind of operation, - // and failing to return it for whatever reason should no longer be considered a - // breaking scenario. In fact, this happens on a regular basis when two calls to - // ReturnProxy are issued, one when handling a raw message signaling a discovery/run - // complete, and one when actually processing this kind of message. As such, only the - // first call will ever succeed, with the second one always failing. Another failing - // scenario was attempting to return a "non-managed" testhost (one that can be obtained, - // for example, by failing to match discovery/run criteria to session criteria, and as - // such an on-demand testhost is spawned) to a test session. A "non-managed" testhost - // has -1 for the Id, and the call to EnqueueProxy will fail and an exception will be - // thrown. We have to make sure we catch that exception instead of relying on the caller - // to perform sanity checks, hence why we expanded the type of exception that we handle - // to generic exceptions too. - EqtTrace.Warning("TestSessionPool.ReturnProxy failed: {0}", ex.ToString()); - } - - return false; - } -} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Events/StartTestSessionCompleteEventArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Events/StartTestSessionCompleteEventArgs.cs deleted file mode 100644 index 011e714b7f..0000000000 --- a/src/Microsoft.TestPlatform.ObjectModel/Client/Events/StartTestSessionCompleteEventArgs.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; - -/// -/// Event arguments used to notify the caller about the status of the test session. -/// -[DataContract] -public class StartTestSessionCompleteEventArgs : EventArgs -{ - /// - /// Gets or sets the test session info. - /// - [DataMember] - public TestSessionInfo? TestSessionInfo { get; set; } - - /// - /// Gets or sets the metrics. - /// - [DataMember] - public IDictionary? Metrics { get; set; } -} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Events/StopTestSessionCompleteEventArgs.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Events/StopTestSessionCompleteEventArgs.cs deleted file mode 100644 index d5eaad8029..0000000000 --- a/src/Microsoft.TestPlatform.ObjectModel/Client/Events/StopTestSessionCompleteEventArgs.cs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; - -/// -/// Event arguments used to notify the caller about the status of the test session. -/// -[DataContract] -public class StopTestSessionCompleteEventArgs : EventArgs -{ - /// - /// Creates an instance of the current class. - /// - public StopTestSessionCompleteEventArgs() - { } - - /// - /// Creates an instance of the current class. - /// - /// - /// The test session info. - public StopTestSessionCompleteEventArgs(TestSessionInfo? testSessionInfo) - { - TestSessionInfo = testSessionInfo; - } - - /// - /// Gets or sets the test session info. - /// - [DataMember] - public TestSessionInfo? TestSessionInfo { get; set; } - - /// - /// Gets or sets the metrics. - /// - [DataMember] - public IDictionary? Metrics { get; set; } - - /// - /// Gets or sets a value indicating if the session was successfully stopped or not. - /// - [DataMember] - public bool IsStopped { get; set; } = false; -} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestPlatform.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestPlatform.cs index 789a84d3f3..76e0404d61 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestPlatform.cs +++ b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestPlatform.cs @@ -66,24 +66,4 @@ ITestRunRequest CreateTestRunRequest( TestPlatformOptions? options, Dictionary sourceToSourceDetailMap, IWarningLogger warningLogger); - - /// - /// Starts a test session. - /// - /// - /// - /// Providing common services and data for test session start. - /// - /// Specifies the start test session criteria. - /// Events handler for handling session events. - /// Details of each dll (source). - /// Logger to use for warnings. - /// - /// True if the operation succeeded, false otherwise. - bool StartTestSession( - IRequestData requestData, - StartTestSessionCriteria criteria, - ITestSessionEventsHandler eventsHandler, - Dictionary sourceToSourceDetailMap, - IWarningLogger warningLogger); } diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestSessionEventsHandler.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestSessionEventsHandler.cs deleted file mode 100644 index d94638a2f8..0000000000 --- a/src/Microsoft.TestPlatform.ObjectModel/Client/Interfaces/ITestSessionEventsHandler.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; - -/// -/// Interface contract for handling test session events. -/// -public interface ITestSessionEventsHandler : ITestMessageEventHandler -{ - /// - /// Dispatch StartTestSession complete event to listeners. - /// - /// - /// The event args. - void HandleStartTestSessionComplete(StartTestSessionCompleteEventArgs? eventArgs); - - /// - /// Dispatch StopTestSession complete event to listeners. - /// - /// - /// The event args. - void HandleStopTestSessionComplete(StopTestSessionCompleteEventArgs? eventArgs); -} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Payloads/StartTestSessionAckPayload.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Payloads/StartTestSessionAckPayload.cs deleted file mode 100644 index 6e3b892e26..0000000000 --- a/src/Microsoft.TestPlatform.ObjectModel/Client/Payloads/StartTestSessionAckPayload.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Runtime.Serialization; - -namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads; - -/// -/// Class used to define the start test session ack payload sent by the design mode client -/// back to the vstest.console translation layers. -/// -[DataContract] -public class StartTestSessionAckPayload -{ - /// - /// Gets or sets the event args. - /// - [DataMember] - public StartTestSessionCompleteEventArgs? EventArgs { get; set; } -} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Payloads/StartTestSessionPayload.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Payloads/StartTestSessionPayload.cs deleted file mode 100644 index eef7d1cd37..0000000000 --- a/src/Microsoft.TestPlatform.ObjectModel/Client/Payloads/StartTestSessionPayload.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; -using System.Runtime.Serialization; - -namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads; - -/// -/// Class used to define the start test session payload sent by the vstest.console translation -/// layers into design mode. -/// -[DataContract] -public class StartTestSessionPayload -{ - /// - /// Gets or sets the sources used for starting the test session. - /// - [DataMember] - public IList? Sources { get; set; } - - /// - /// Gets or sets the run settings used for starting the test session. - /// - [DataMember] - public string? RunSettings { get; set; } - - /// - /// Gets or sets a flag indicating if debugging is enabled. - /// - [DataMember] - public bool IsDebuggingEnabled { get; set; } - - /// - /// Gets or sets a flag indicating if a custom host launcher should be used. - /// - [DataMember] - public bool HasCustomHostLauncher { get; set; } - - /// - /// Gets or sets the test platform options. - /// - [DataMember] - public TestPlatformOptions? TestPlatformOptions { get; set; } -} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Payloads/StopTestSessionAckPayload.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Payloads/StopTestSessionAckPayload.cs deleted file mode 100644 index fbc266aa78..0000000000 --- a/src/Microsoft.TestPlatform.ObjectModel/Client/Payloads/StopTestSessionAckPayload.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Runtime.Serialization; - -namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads; - -/// -/// Class used to define the stop test session ack payload sent by the design mode client -/// back to the vstest.console translation layers. -/// -[DataContract] -public class StopTestSessionAckPayload -{ - /// - /// Gets or sets the event args. - /// - [DataMember] - public StopTestSessionCompleteEventArgs? EventArgs { get; set; } -} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/Payloads/StopTestSessionPayload.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/Payloads/StopTestSessionPayload.cs deleted file mode 100644 index c09f3be1ca..0000000000 --- a/src/Microsoft.TestPlatform.ObjectModel/Client/Payloads/StopTestSessionPayload.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Runtime.Serialization; - -namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads; - -/// -/// Class used to define the stop test session payload sent by the vstest.console translation -/// layers into design mode. -/// -[DataContract] -public class StopTestSessionPayload -{ - /// - /// Gets or sets the test session info. - /// - [DataMember] - public TestSessionInfo? TestSessionInfo { get; set; } - - /// - /// Gets or sets a flag indicating if metrics should be collected. - /// - [DataMember] - public bool CollectMetrics { get; set; } -} diff --git a/src/Microsoft.TestPlatform.ObjectModel/Client/StartTestSessionCriteria.cs b/src/Microsoft.TestPlatform.ObjectModel/Client/StartTestSessionCriteria.cs deleted file mode 100644 index 2516ee4907..0000000000 --- a/src/Microsoft.TestPlatform.ObjectModel/Client/StartTestSessionCriteria.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System.Collections.Generic; -using System.Runtime.Serialization; - -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; - -namespace Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; - -/// -/// Class used to define the start test session criteria. -/// -[DataContract] -public class StartTestSessionCriteria -{ - /// - /// Gets or sets the sources used for starting the test session. - /// - [DataMember] - public IList? Sources { get; set; } - - /// - /// Gets or sets the run settings used for starting the test session. - /// - [DataMember] - public string? RunSettings { get; set; } - - /// - /// Gets or sets the test host launcher used for starting the test session. - /// - [DataMember] - public ITestHostLauncher? TestHostLauncher { get; set; } -} diff --git a/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Unshipped.txt b/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Unshipped.txt index 355d96ae9b..f8340628a2 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/Microsoft.TestPlatform.ObjectModel/PublicAPI/PublicAPI.Unshipped.txt @@ -15,3 +15,56 @@ Microsoft.VisualStudio.TestPlatform.ObjectModel.TelemetryEvent.Name.get -> strin Microsoft.VisualStudio.TestPlatform.ObjectModel.TelemetryEvent.Properties.get -> System.Collections.Generic.IDictionary! Microsoft.VisualStudio.TestPlatform.ObjectModel.TelemetryEvent.TelemetryEvent(string! name, System.Collections.Generic.IDictionary! properties) -> void Microsoft.VisualStudio.TestPlatform.ObjectModel.RunConfiguration.CreateNoNewWindow.get -> bool +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestPlatform.StartTestSession(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IRequestData! requestData, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCriteria! criteria, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestSessionEventsHandler! eventsHandler, System.Collections.Generic.Dictionary! sourceToSourceDetailMap, Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.IWarningLogger! warningLogger) -> bool +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestSessionEventsHandler +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestSessionEventsHandler.HandleStartTestSessionComplete(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCompleteEventArgs? eventArgs) -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.ITestSessionEventsHandler.HandleStopTestSessionComplete(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StopTestSessionCompleteEventArgs? eventArgs) -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StartTestSessionAckPayload +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StartTestSessionAckPayload.EventArgs.get -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCompleteEventArgs? +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StartTestSessionAckPayload.EventArgs.set -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StartTestSessionAckPayload.StartTestSessionAckPayload() -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StartTestSessionPayload +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StartTestSessionPayload.HasCustomHostLauncher.get -> bool +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StartTestSessionPayload.HasCustomHostLauncher.set -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StartTestSessionPayload.IsDebuggingEnabled.get -> bool +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StartTestSessionPayload.IsDebuggingEnabled.set -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StartTestSessionPayload.RunSettings.get -> string? +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StartTestSessionPayload.RunSettings.set -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StartTestSessionPayload.Sources.get -> System.Collections.Generic.IList? +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StartTestSessionPayload.Sources.set -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StartTestSessionPayload.StartTestSessionPayload() -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StartTestSessionPayload.TestPlatformOptions.get -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestPlatformOptions? +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StartTestSessionPayload.TestPlatformOptions.set -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StopTestSessionAckPayload +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StopTestSessionAckPayload.EventArgs.get -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StopTestSessionCompleteEventArgs? +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StopTestSessionAckPayload.EventArgs.set -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StopTestSessionAckPayload.StopTestSessionAckPayload() -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StopTestSessionPayload +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StopTestSessionPayload.CollectMetrics.get -> bool +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StopTestSessionPayload.CollectMetrics.set -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StopTestSessionPayload.StopTestSessionPayload() -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCompleteEventArgs +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCompleteEventArgs.Metrics.get -> System.Collections.Generic.IDictionary? +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCompleteEventArgs.Metrics.set -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCompleteEventArgs.StartTestSessionCompleteEventArgs() -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCriteria +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCriteria.RunSettings.get -> string? +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCriteria.RunSettings.set -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCriteria.Sources.get -> System.Collections.Generic.IList? +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCriteria.Sources.set -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCriteria.StartTestSessionCriteria() -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCriteria.TestHostLauncher.get -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces.ITestHostLauncher? +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCriteria.TestHostLauncher.set -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StopTestSessionCompleteEventArgs +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StopTestSessionCompleteEventArgs.IsStopped.get -> bool +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StopTestSessionCompleteEventArgs.IsStopped.set -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StopTestSessionCompleteEventArgs.Metrics.get -> System.Collections.Generic.IDictionary? +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StopTestSessionCompleteEventArgs.Metrics.set -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StopTestSessionCompleteEventArgs.StopTestSessionCompleteEventArgs() -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StopTestSessionPayload.TestSessionInfo.get -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestSessionInfo? +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Payloads.StopTestSessionPayload.TestSessionInfo.set -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCompleteEventArgs.TestSessionInfo.get -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestSessionInfo? +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StartTestSessionCompleteEventArgs.TestSessionInfo.set -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StopTestSessionCompleteEventArgs.StopTestSessionCompleteEventArgs(Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestSessionInfo? testSessionInfo) -> void +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StopTestSessionCompleteEventArgs.TestSessionInfo.get -> Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.TestSessionInfo? +*REMOVED*Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.StopTestSessionCompleteEventArgs.TestSessionInfo.set -> void diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITestSession.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITestSession.cs deleted file mode 100644 index c57b8761d2..0000000000 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITestSession.cs +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; - -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; - -namespace Microsoft.VisualStudio.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; - -/// -/// Defines a test session that can be used to make calls to the vstest.console -/// process. -/// -[Obsolete("This API is not final yet and is subject to changes.", false)] -public interface ITestSession : IDisposable, ITestSessionAsync -{ - /// - /// Gets the underlying test session info object. - /// - [Obsolete("This API is not final yet and is subject to changes.", false)] - TestSessionInfo? TestSessionInfo { get; } - - /// - /// Starts test discovery. - /// - /// - /// The list of source assemblies for the discovery. - /// The run settings for the discovery. - /// The discovery event handler. - [Obsolete("This API is not final yet and is subject to changes.", false)] - void DiscoverTests( - IEnumerable sources, - string discoverySettings, - ITestDiscoveryEventsHandler discoveryEventsHandler); - - /// - /// Starts test discovery. - /// - /// - /// The list of source assemblies for the discovery. - /// The run settings for the discovery. - /// The test platform options. - /// The discovery event handler. - [Obsolete("This API is not final yet and is subject to changes.", false)] - void DiscoverTests( - IEnumerable sources, - string discoverySettings, - TestPlatformOptions options, - ITestDiscoveryEventsHandler2 discoveryEventsHandler); - - /// - /// Cancels the last discovery request. - /// - [Obsolete("This API is not final yet and is subject to changes.", false)] - new void CancelDiscovery(); - - /// - /// Starts a test run. - /// - /// - /// The list of source assemblies for the test run. - /// The run settings for the run. - /// The run event handler. - [Obsolete("This API is not final yet and is subject to changes.", false)] - void RunTests( - IEnumerable sources, - string runSettings, - ITestRunEventsHandler testRunEventsHandler); - - /// - /// Starts a test run. - /// - /// - /// The list of source assemblies for the test run. - /// The run settings for the run. - /// The test platform options. - /// The run event handler. - [Obsolete("This API is not final yet and is subject to changes.", false)] - void RunTests( - IEnumerable sources, - string runSettings, - TestPlatformOptions options, - ITestRunEventsHandler testRunEventsHandler); - - /// - /// Starts a test run. - /// - /// - /// The list of source assemblies for the test run. - /// The run settings for the run. - /// The test platform options. - /// The run event handler. - /// The telemetry event handler. - [Obsolete("This API is not final yet and is subject to changes.", false)] - void RunTests( - IEnumerable sources, - string runSettings, - TestPlatformOptions options, - ITestRunEventsHandler testRunEventsHandler, - ITelemetryEventsHandler telemetryEventsHandler); - - /// - /// Starts a test run. - /// - /// - /// The list of test cases for the test run. - /// The run settings for the run. - /// The run event handler. - [Obsolete("This API is not final yet and is subject to changes.", false)] - void RunTests( - IEnumerable testCases, - string runSettings, - ITestRunEventsHandler testRunEventsHandler); - - /// - /// Starts a test run. - /// - /// - /// The list of test cases for the test run. - /// The run settings for the run. - /// The test platform options. - /// The run event handler. - [Obsolete("This API is not final yet and is subject to changes.", false)] - void RunTests( - IEnumerable testCases, - string runSettings, - TestPlatformOptions options, - ITestRunEventsHandler testRunEventsHandler); - - /// - /// Starts a test run. - /// - /// - /// The list of test cases for the test run. - /// The run settings for the run. - /// The test platform options. - /// The run event handler. - /// The telemetry event handler. - [Obsolete("This API is not final yet and is subject to changes.", false)] - void RunTests( - IEnumerable testCases, - string runSettings, - TestPlatformOptions options, - ITestRunEventsHandler testRunEventsHandler, - ITelemetryEventsHandler telemetryEventsHandler); - - /// - /// Starts a test run. - /// - /// - /// The list of source assemblies for the test run. - /// The run settings for the run. - /// The run event handler. - /// The custom host launcher. - [Obsolete("This API is not final yet and is subject to changes.", false)] - void RunTestsWithCustomTestHost( - IEnumerable sources, - string runSettings, - ITestRunEventsHandler testRunEventsHandler, - ITestHostLauncher customTestHostLauncher); - - /// - /// Starts a test run. - /// - /// - /// The list of source assemblies for the test run. - /// The run settings for the run. - /// The test platform options. - /// The run event handler. - /// The custom host launcher. - [Obsolete("This API is not final yet and is subject to changes.", false)] - void RunTestsWithCustomTestHost( - IEnumerable sources, - string runSettings, - TestPlatformOptions options, - ITestRunEventsHandler testRunEventsHandler, - ITestHostLauncher customTestHostLauncher); - - /// - /// Starts a test run. - /// - /// - /// The list of source assemblies for the test run. - /// The run settings for the run. - /// The test platform options. - /// The run event handler. - /// The telemetry event handler. - /// The custom host launcher. - [Obsolete("This API is not final yet and is subject to changes.", false)] - void RunTestsWithCustomTestHost( - IEnumerable sources, - string runSettings, - TestPlatformOptions options, - ITestRunEventsHandler testRunEventsHandler, - ITelemetryEventsHandler telemetryEventsHandler, - ITestHostLauncher customTestHostLauncher); - - /// - /// Starts a test run. - /// - /// - /// The list of test cases for the test run. - /// The run settings for the run. - /// The run event handler. - /// The custom host launcher. - [Obsolete("This API is not final yet and is subject to changes.", false)] - void RunTestsWithCustomTestHost( - IEnumerable testCases, - string runSettings, - ITestRunEventsHandler testRunEventsHandler, - ITestHostLauncher customTestHostLauncher); - - /// - /// Starts a test run. - /// - /// - /// The list of test cases for the test run. - /// The run settings for the run. - /// The test platform options. - /// The run event handler. - /// The custom host launcher. - [Obsolete("This API is not final yet and is subject to changes.", false)] - void RunTestsWithCustomTestHost( - IEnumerable testCases, - string runSettings, - TestPlatformOptions options, - ITestRunEventsHandler testRunEventsHandler, - ITestHostLauncher customTestHostLauncher); - - /// - /// Starts a test run. - /// - /// - /// The list of test cases for the test run. - /// The run settings for the run. - /// The test platform options. - /// The run event handler. - /// The telemetry event handler. - /// The custom host launcher. - [Obsolete("This API is not final yet and is subject to changes.", false)] - void RunTestsWithCustomTestHost( - IEnumerable testCases, - string runSettings, - TestPlatformOptions options, - ITestRunEventsHandler testRunEventsHandler, - ITelemetryEventsHandler telemetryEventsHandler, - ITestHostLauncher customTestHostLauncher); - - /// - /// Stops the test session. - /// - /// - /// True if the session was successfuly stopped, false otherwise. - [Obsolete("This API is not final yet and is subject to changes.", false)] - bool StopTestSession(); - - /// - /// Stops the test session. - /// - /// - /// The session event handler. - /// - /// True if the session was successfuly stopped, false otherwise. - [Obsolete("This API is not final yet and is subject to changes.", false)] - bool StopTestSession(ITestSessionEventsHandler eventsHandler); - - /// - /// Stops the test session. - /// - /// - /// Test Platform options. - /// The session event handler. - /// - /// True if the session was successfuly stopped, false otherwise. - [Obsolete("This API is not final yet and is subject to changes.", false)] - bool StopTestSession(TestPlatformOptions options, ITestSessionEventsHandler eventsHandler); - - /// - /// Cancels the last test run. - /// - [Obsolete("This API is not final yet and is subject to changes.", false)] - new void CancelTestRun(); - - /// - /// Aborts the last test run. - /// - [Obsolete("This API is not final yet and is subject to changes.", false)] - new void AbortTestRun(); -} diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITestSessionAsync.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITestSessionAsync.cs deleted file mode 100644 index b97ed5fe21..0000000000 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITestSessionAsync.cs +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -using Microsoft.VisualStudio.TestPlatform.ObjectModel; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; - -namespace Microsoft.VisualStudio.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; - -/// -/// Defines a test session that can be used to make async calls to the vstest.console -/// process. -/// -[Obsolete("This API is not final yet and is subject to changes.", false)] -public interface ITestSessionAsync : IDisposable -{ - /// - /// Starts test discovery. - /// - /// - /// The list of source assemblies for the discovery. - /// The run settings for the discovery. - /// The discovery event handler. - /// - [Obsolete("This API is not final yet and is subject to changes.", false)] - Task DiscoverTestsAsync( - IEnumerable sources, - string discoverySettings, - ITestDiscoveryEventsHandler discoveryEventsHandler); - - /// - /// Starts test discovery. - /// - /// - /// The list of source assemblies for the discovery. - /// The run settings for the discovery. - /// The test platform options. - /// The discovery event handler. - [Obsolete("This API is not final yet and is subject to changes.", false)] - Task DiscoverTestsAsync( - IEnumerable sources, - string discoverySettings, - TestPlatformOptions options, - ITestDiscoveryEventsHandler2 discoveryEventsHandler); - - /// - /// Cancels the last discovery request. - /// - [Obsolete("This API is not final yet and is subject to changes.", false)] - void CancelDiscovery(); - - /// - /// Starts a test run. - /// - /// - /// The list of source assemblies for the test run. - /// The run settings for the run. - /// The run event handler. - [Obsolete("This API is not final yet and is subject to changes.", false)] - Task RunTestsAsync( - IEnumerable sources, - string runSettings, - ITestRunEventsHandler testRunEventsHandler); - - /// - /// Starts a test run. - /// - /// - /// The list of source assemblies for the test run. - /// The run settings for the run. - /// The test platform options. - /// The run event handler. - [Obsolete("This API is not final yet and is subject to changes.", false)] - Task RunTestsAsync( - IEnumerable sources, - string runSettings, - TestPlatformOptions options, - ITestRunEventsHandler testRunEventsHandler); - - /// - /// Starts a test run. - /// - /// - /// The list of source assemblies for the test run. - /// The run settings for the run. - /// The test platform options. - /// The run event handler. - /// The telemetry event handler. - [Obsolete("This API is not final yet and is subject to changes.", false)] - Task RunTestsAsync( - IEnumerable sources, - string runSettings, - TestPlatformOptions options, - ITestRunEventsHandler testRunEventsHandler, - ITelemetryEventsHandler telemetryEventsHandler); - - /// - /// Starts a test run. - /// - /// - /// The list of test cases for the test run. - /// The run settings for the run. - /// The run event handler. - [Obsolete("This API is not final yet and is subject to changes.", false)] - Task RunTestsAsync( - IEnumerable testCases, - string runSettings, - ITestRunEventsHandler testRunEventsHandler); - - /// - /// Starts a test run. - /// - /// - /// The list of test cases for the test run. - /// The run settings for the run. - /// The test platform options. - /// The run event handler. - [Obsolete("This API is not final yet and is subject to changes.", false)] - Task RunTestsAsync( - IEnumerable testCases, - string runSettings, - TestPlatformOptions options, - ITestRunEventsHandler testRunEventsHandler); - - /// - /// Starts a test run. - /// - /// - /// The list of test cases for the test run. - /// The run settings for the run. - /// The test platform options. - /// The run event handler. - /// The telemetry event handler. - [Obsolete("This API is not final yet and is subject to changes.", false)] - Task RunTestsAsync( - IEnumerable testCases, - string runSettings, - TestPlatformOptions options, - ITestRunEventsHandler testRunEventsHandler, - ITelemetryEventsHandler telemetryEventsHandler); - - /// - /// Starts a test run. - /// - /// - /// The list of source assemblies for the test run. - /// The run settings for the run. - /// The run event handler. - /// The custom host launcher. - [Obsolete("This API is not final yet and is subject to changes.", false)] - Task RunTestsWithCustomTestHostAsync( - IEnumerable sources, - string runSettings, - ITestRunEventsHandler testRunEventsHandler, - ITestHostLauncher customTestHostLauncher); - - /// - /// Starts a test run. - /// - /// - /// The list of source assemblies for the test run. - /// The run settings for the run. - /// The test platform options. - /// The run event handler. - /// The custom host launcher. - [Obsolete("This API is not final yet and is subject to changes.", false)] - Task RunTestsWithCustomTestHostAsync( - IEnumerable sources, - string runSettings, - TestPlatformOptions options, - ITestRunEventsHandler testRunEventsHandler, - ITestHostLauncher customTestHostLauncher); - - /// - /// Starts a test run. - /// - /// - /// The list of source assemblies for the test run. - /// The run settings for the run. - /// The test platform options. - /// The run event handler. - /// The telemetry event handler. - /// The custom host launcher. - [Obsolete("This API is not final yet and is subject to changes.", false)] - Task RunTestsWithCustomTestHostAsync( - IEnumerable sources, - string runSettings, - TestPlatformOptions options, - ITestRunEventsHandler testRunEventsHandler, - ITelemetryEventsHandler telemetryEventsHandler, - ITestHostLauncher customTestHostLauncher); - - /// - /// Starts a test run. - /// - /// - /// The list of test cases for the test run. - /// The run settings for the run. - /// The run event handler. - /// The custom host launcher. - [Obsolete("This API is not final yet and is subject to changes.", false)] - Task RunTestsWithCustomTestHostAsync( - IEnumerable testCases, - string runSettings, - ITestRunEventsHandler testRunEventsHandler, - ITestHostLauncher customTestHostLauncher); - - /// - /// Starts a test run. - /// - /// - /// The list of test cases for the test run. - /// The run settings for the run. - /// The test platform options. - /// The run event handler. - /// The custom host launcher. - [Obsolete("This API is not final yet and is subject to changes.", false)] - Task RunTestsWithCustomTestHostAsync( - IEnumerable testCases, - string runSettings, - TestPlatformOptions options, - ITestRunEventsHandler testRunEventsHandler, - ITestHostLauncher customTestHostLauncher); - - /// - /// Starts a test run. - /// - /// - /// The list of test cases for the test run. - /// The run settings for the run. - /// The test platform options. - /// The run event handler. - /// The telemetry event handler. - /// The custom host launcher. - [Obsolete("This API is not final yet and is subject to changes.", false)] - Task RunTestsWithCustomTestHostAsync( - IEnumerable testCases, - string runSettings, - TestPlatformOptions options, - ITestRunEventsHandler testRunEventsHandler, - ITelemetryEventsHandler telemetryEventsHandler, - ITestHostLauncher customTestHostLauncher); - - /// - /// Stops the test session. - /// - /// - /// True if the session was successfuly stopped, false otherwise. - [Obsolete("This API is not final yet and is subject to changes.", false)] - Task StopTestSessionAsync(); - - /// - /// Stops the test session. - /// - /// - /// The session event handler. - /// - /// True if the session was successfuly stopped, false otherwise. - [Obsolete("This API is not final yet and is subject to changes.", false)] - Task StopTestSessionAsync( - ITestSessionEventsHandler eventsHandler); - - /// - /// Stops the test session. - /// - /// - /// Test Platform options. - /// The session event handler. - /// - /// True if the session was successfuly stopped, false otherwise. - [Obsolete("This API is not final yet and is subject to changes.", false)] - Task StopTestSessionAsync( - TestPlatformOptions options, - ITestSessionEventsHandler eventsHandler); - - /// - /// Cancels the last test run. - /// - [Obsolete("This API is not final yet and is subject to changes.", false)] - void CancelTestRun(); - - /// - /// Aborts the last test run. - /// - [Obsolete("This API is not final yet and is subject to changes.", false)] - void AbortTestRun(); -} diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSender.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSender.cs index 2a606f21e6..348015d729 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSender.cs +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSender.cs @@ -136,35 +136,6 @@ void StartTestRunWithCustomHost( ITelemetryEventsHandler telemetryEventsHandler, ITestHostLauncher customTestHostLauncher); - /// - /// Starts a new test session. - /// - /// - /// Sources for test run. - /// Run settings for test run. - /// Options to be passed into the platform. - /// Event handler for test session events. - /// Custom test host launcher. - /// - TestSessionInfo? StartTestSession( - IList sources, - string? runSettings, - TestPlatformOptions? options, - ITestSessionEventsHandler eventsHandler, - ITestHostLauncher? testHostLauncher); - - /// - /// Stops the test session. - /// - /// - /// Test session info. - /// Test Platform options. - /// Event handler for test session events. - bool StopTestSession( - TestSessionInfo? testSessionInfo, - TestPlatformOptions? options, - ITestSessionEventsHandler eventsHandler); - /// /// Ends the session. /// diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSenderAsync.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSenderAsync.cs index 161ecb31bb..10df9a7008 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSenderAsync.cs +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/ITranslationLayerRequestSenderAsync.cs @@ -112,34 +112,6 @@ Task StartTestRunWithCustomHostAsync( ITelemetryEventsHandler telemetryEventsHandler, ITestHostLauncher customTestHostLauncher); - /// - /// Asynchronous equivalent of . - /// - Task StartTestSessionAsync( - IList sources, - string? runSettings, - TestPlatformOptions? options, - ITestSessionEventsHandler eventsHandler, - ITestHostLauncher? testHostLauncher); - - /// - /// Asynchronous equivalent of . - /// - Task StopTestSessionAsync( - TestSessionInfo? testSessionInfo, - TestPlatformOptions? options, - ITestSessionEventsHandler eventsHandler); - /// /// Provides back all attachments to test platform for additional processing (for example /// merging). diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IVsTestConsoleWrapper.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IVsTestConsoleWrapper.cs index ed4754b130..7cccadd417 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IVsTestConsoleWrapper.cs +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IVsTestConsoleWrapper.cs @@ -7,7 +7,6 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; -using Microsoft.VisualStudio.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; @@ -21,85 +20,6 @@ public interface IVsTestConsoleWrapper : IVsTestConsoleWrapperAsync /// void StartSession(); - /// - /// Starts a new test session. - /// - /// - /// The list of source assemblies for the test run. - /// The run settings for the run. - /// The session event handler. - /// - /// A test session info object. - [Obsolete("This API is not final yet and is subject to changes.", false)] - ITestSession? StartTestSession( - IList sources, - string? runSettings, - ITestSessionEventsHandler eventsHandler); - - /// - /// Starts a new test session. - /// - /// - /// The list of source assemblies for the test run. - /// The run settings for the run. - /// The test platform options. - /// The session event handler. - /// - /// A test session info object. - [Obsolete("This API is not final yet and is subject to changes.", false)] - ITestSession? StartTestSession( - IList sources, - string? runSettings, - TestPlatformOptions? options, - ITestSessionEventsHandler eventsHandler); - - /// - /// Starts a new test session. - /// - /// - /// The list of source assemblies for the test run. - /// The run settings for the run. - /// The test platform options. - /// The session event handler. - /// The custom host launcher. - /// - /// A test session info object. - [Obsolete("This API is not final yet and is subject to changes.", false)] - ITestSession? StartTestSession( - IList sources, - string? runSettings, - TestPlatformOptions? options, - ITestSessionEventsHandler eventsHandler, - ITestHostLauncher testHostLauncher); - - /// - /// Stops the test session. - /// - /// - /// The test session info object. - /// The session event handler. - /// - /// True if the session was successfuly stopped, false otherwise. - [Obsolete("This API is not final yet and is subject to changes.", false)] - bool StopTestSession( - TestSessionInfo? testSessionInfo, - ITestSessionEventsHandler eventsHandler); - - /// - /// Stops the test session. - /// - /// - /// The test session info object. - /// Test Platform options. - /// The session event handler. - /// - /// True if the session was successfuly stopped, false otherwise. - [Obsolete("This API is not final yet and is subject to changes.", false)] - bool StopTestSession( - TestSessionInfo? testSessionInfo, - TestPlatformOptions? options, - ITestSessionEventsHandler eventsHandler); - /// /// Initializes the test platform with paths to extensions like adapters, loggers and any /// other extensions. diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IVsTestConsoleWrapperAsync.cs b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IVsTestConsoleWrapperAsync.cs index 88179eb7e6..69c8e04360 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IVsTestConsoleWrapperAsync.cs +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Interfaces/IVsTestConsoleWrapperAsync.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; @@ -9,7 +9,6 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; -using Microsoft.VisualStudio.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; namespace Microsoft.TestPlatform.VsTestConsole.TranslationLayer.Interfaces; @@ -24,75 +23,6 @@ public interface IVsTestConsoleWrapperAsync [Obsolete("The async APIs don't work, use the sync API instead.")] Task StartSessionAsync(); - /// - /// Asynchronous equivalent of . - /// - [Obsolete("The async APIs don't work, use the sync API instead.")] - Task StartTestSessionAsync( - IList sources, - string? runSettings, - ITestSessionEventsHandler eventsHandler); - - /// - /// Asynchronous equivalent of . - /// - [Obsolete("The async APIs don't work, use the sync API instead.")] - Task StartTestSessionAsync( - IList sources, - string? runSettings, - TestPlatformOptions? options, - ITestSessionEventsHandler eventsHandler); - - /// - /// Asynchronous equivalent of . - /// - [Obsolete("The async APIs don't work, use the sync API instead.")] - Task StartTestSessionAsync( - IList sources, - string? runSettings, - TestPlatformOptions? options, - ITestSessionEventsHandler eventsHandler, - ITestHostLauncher testHostLauncher); - - /// - /// Asynchronous equivalent of . - /// - [Obsolete("The async APIs don't work, use the sync API instead.")] - Task StopTestSessionAsync( - TestSessionInfo? testSessionInfo, - ITestSessionEventsHandler eventsHandler); - - /// - /// Asynchronous equivalent of . - /// - [Obsolete("This API is not final yet and is subject to changes.", false)] - Task StopTestSessionAsync( - TestSessionInfo? testSessionInfo, - TestPlatformOptions? options, - ITestSessionEventsHandler eventsHandler); - /// /// Asynchronous equivalent of - /// Looks up a localized string similar to The Stop Test Session operation was aborted.. - /// - public static string AbortedStopTestSession { - get { - return ResourceManager.GetString("AbortedStopTestSession", resourceCulture); - } - } - /// /// Looks up a localized string similar to The active Test Run Attachments Processing was aborted.. /// diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/Resources.resx b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/Resources.resx index 5a6313b3fd..6fec8bede9 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/Resources.resx +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Resources/Resources.resx @@ -1,4 +1,4 @@ - + + + false + + + + $(TargetsForTfmSpecificContentInPackage);_IncludePortableNetFxRunnerContent;_IncludePortableNetFxTestHostContent;_IncludePortableNetFxExtensionContent;_IncludePortableNetFxDumpContent;_IncludePortableNetRunnerContent;_IncludePortableNetDataCollectorContent;_IncludePortableNetExtensionContent;_IncludePortableNetDumpContent;_IncludePortableNetFrameworkTestHostPublishContent;_IncludePortableNetFrameworkTestHostContent + + + + + + <_PortableNetFxRunnerPublishDir>$(TestPlatformPackagingPublishRoot)vstest.console\$(NetFrameworkRunnerTargetFramework)\ + + + + <_PortableNetFxRunnerPublishedFile Include="$(_PortableNetFxRunnerPublishDir)**\*" + Exclude="$(_PortableNetFxRunnerPublishDir)**\*.xml;$(_PortableNetFxRunnerPublishDir)System.*.dll;$(_PortableNetFxRunnerPublishDir)Microsoft.Extensions.FileSystemGlobbing.dll" /> + + tools\net462\%(RecursiveDir)%(Filename)%(Extension) + + + + tools\net462\Microsoft.TestPlatform.VsTestConsole.TranslationLayer.xml + + + + + + + + <_PortableNetFxTestHostDefault Include="$(ArtifactsBinDir)testhost\$(Configuration)\net462\win7-x64\testhost.exe;$(ArtifactsBinDir)testhost\$(Configuration)\net462\win7-x64\testhost.exe.config;$(ArtifactsBinDir)testhost\$(Configuration)\net47\win7-x64\testhost.net47.exe;$(ArtifactsBinDir)testhost\$(Configuration)\net47\win7-x64\testhost.net47.exe.config;$(ArtifactsBinDir)testhost\$(Configuration)\net471\win7-x64\testhost.net471.exe;$(ArtifactsBinDir)testhost\$(Configuration)\net471\win7-x64\testhost.net471.exe.config;$(ArtifactsBinDir)testhost\$(Configuration)\net472\win7-x64\testhost.net472.exe;$(ArtifactsBinDir)testhost\$(Configuration)\net472\win7-x64\testhost.net472.exe.config;$(ArtifactsBinDir)testhost\$(Configuration)\net48\win7-x64\testhost.net48.exe;$(ArtifactsBinDir)testhost\$(Configuration)\net48\win7-x64\testhost.net48.exe.config;$(ArtifactsBinDir)testhost\$(Configuration)\net481\win7-x64\testhost.net481.exe;$(ArtifactsBinDir)testhost\$(Configuration)\net481\win7-x64\testhost.net481.exe.config"> + tools\net462\%(Filename)%(Extension) + + <_PortableNetFxTestHostX86 Include="$(ArtifactsBinDir)testhost.x86\$(Configuration)\net462\win-x86\testhost.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net462\win-x86\testhost.x86.exe.config;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net47\win-x86\testhost.net47.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net47\win-x86\testhost.net47.x86.exe.config;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net471\win-x86\testhost.net471.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net471\win-x86\testhost.net471.x86.exe.config;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net472\win-x86\testhost.net472.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net472\win-x86\testhost.net472.x86.exe.config;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net48\win-x86\testhost.net48.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net48\win-x86\testhost.net48.x86.exe.config;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net481\win-x86\testhost.net481.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net481\win-x86\testhost.net481.x86.exe.config"> + tools\net462\%(Filename)%(Extension) + + <_PortableNetFxTestHostArm64 Include="$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net462\win10-arm64\testhost.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net462\win10-arm64\testhost.arm64.exe.config;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net47\win10-arm64\testhost.net47.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net47\win10-arm64\testhost.net47.arm64.exe.config;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net471\win10-arm64\testhost.net471.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net471\win10-arm64\testhost.net471.arm64.exe.config;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net472\win10-arm64\testhost.net472.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net472\win10-arm64\testhost.net472.arm64.exe.config;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net48\win10-arm64\testhost.net48.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net48\win10-arm64\testhost.net48.arm64.exe.config;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net481\win10-arm64\testhost.net481.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net481\win10-arm64\testhost.net481.arm64.exe.config"> + tools\net462\%(Filename)%(Extension) + + <_PortableNetFxToolContent Include="$(ArtifactsBinDir)datacollector\$(Configuration)\net48\win7-x64\datacollector.exe;$(ArtifactsBinDir)datacollector\$(Configuration)\net48\win7-x64\datacollector.exe.config;$(ArtifactsBinDir)datacollector.arm64\$(Configuration)\net48\win10-arm64\datacollector.arm64.exe;$(ArtifactsBinDir)datacollector.arm64\$(Configuration)\net48\win10-arm64\datacollector.arm64.exe.config;$(ArtifactsBinDir)vstest.console.arm64\$(Configuration)\net48\win10-arm64\vstest.console.arm64.exe;$(ArtifactsBinDir)vstest.console.arm64\$(Configuration)\net48\win10-arm64\vstest.console.arm64.exe.config"> + tools\net462\%(Filename)%(Extension) + + + + + + + + + <_PortableNetFxBlame Include="$(ArtifactsBinDir)Microsoft.TestPlatform.Extensions.BlameDataCollector\$(Configuration)\net48\**\Microsoft.TestPlatform.Extensions.BlameDataCollector*.dll"> + tools\net462\Extensions\%(RecursiveDir)%(Filename)%(Extension) + + <_PortableNetFxHtml Include="$(ArtifactsBinDir)Microsoft.TestPlatform.Extensions.HtmlLogger\$(Configuration)\net48\**\Microsoft.VisualStudio.TestPlatform.Extensions.Html.TestLogger*.dll"> + tools\net462\Extensions\%(RecursiveDir)%(Filename)%(Extension) + + <_PortableNetFxTrx Include="$(ArtifactsBinDir)Microsoft.TestPlatform.Extensions.TrxLogger\$(Configuration)\net462\**\Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger*.dll"> + tools\net462\Extensions\%(RecursiveDir)%(Filename)%(Extension) + + <_PortableNetFxTestHostProvider Include="$(ArtifactsBinDir)Microsoft.TestPlatform.TestHostProvider\$(Configuration)\net48\**\Microsoft.TestPlatform.TestHostRuntimeProvider*.dll"> + tools\net462\Extensions\%(RecursiveDir)%(Filename)%(Extension) + + <_PortableNetFxEventLog Include="$(ArtifactsBinDir)Microsoft.TestPlatform.Extensions.EventLogCollector\$(Configuration)\net48\**\Microsoft.TestPlatform.Extensions.EventLogCollector.resources.dll"> + tools\net462\Extensions\%(RecursiveDir)%(Filename)%(Extension) + + + + + + + + + <_PortableNetFxDump Include="$(ArtifactsBinDir)DumpMinitool\$(Configuration)\$(NetFrameworkMinimum)\win7-x64\DumpMinitool.exe;$(ArtifactsBinDir)DumpMinitool\$(Configuration)\$(NetFrameworkMinimum)\win7-x64\DumpMinitool.exe.config;$(ArtifactsBinDir)DumpMinitool.x86\$(Configuration)\$(NetFrameworkMinimum)\win-x86\DumpMinitool.x86.exe;$(ArtifactsBinDir)DumpMinitool.x86\$(Configuration)\$(NetFrameworkMinimum)\win-x86\DumpMinitool.x86.exe.config;$(ArtifactsBinDir)DumpMinitool.arm64\$(Configuration)\$(NetFrameworkMinimum)\win10-arm64\DumpMinitool.arm64.exe;$(ArtifactsBinDir)DumpMinitool.arm64\$(Configuration)\$(NetFrameworkMinimum)\win10-arm64\DumpMinitool.arm64.exe.config"> + tools\net462\Extensions\dump\%(Filename)%(Extension) + + + + + + + + + <_PortableNetRunnerPublishDir>$(TestPlatformPackagingPublishRoot)vstest.console\$(NetCoreAppMinimum)\ + + + + <_PortableNetRunnerPublishedFile Include="$(_PortableNetRunnerPublishDir)**\*" + Exclude="$(_PortableNetRunnerPublishDir)**\*.xml;$(_PortableNetRunnerPublishDir)System.*.dll;$(_PortableNetRunnerPublishDir)vstest.console.exe;$(_PortableNetRunnerPublishDir)Microsoft.Extensions.FileSystemGlobbing.dll" /> + + tools\net8.0\%(RecursiveDir)%(Filename)%(Extension) + + + + tools\net8.0\Microsoft.TestPlatform.VsTestConsole.TranslationLayer.xml + + + + + + + + <_PortableNetDataCollector Include="$(ArtifactsBinDir)datacollector\$(Configuration)\$(NetCoreAppMinimum)\datacollector.dll;$(ArtifactsBinDir)datacollector\$(Configuration)\$(NetCoreAppMinimum)\datacollector.dll.config;$(ArtifactsBinDir)datacollector\$(Configuration)\$(NetCoreAppMinimum)\datacollector.deps.json;$(ArtifactsBinDir)datacollector\$(Configuration)\$(NetCoreAppMinimum)\datacollector.runtimeconfig.json"> + tools\net8.0\%(Filename)%(Extension) + + + + + + + + + <_PortableNetBlame Include="$(ArtifactsBinDir)Microsoft.TestPlatform.Extensions.BlameDataCollector\$(Configuration)\netstandard2.0\Microsoft.TestPlatform.Extensions.BlameDataCollector.dll"> + tools\net8.0\Extensions\%(Filename)%(Extension) + + <_PortableNetHtml Include="$(ArtifactsBinDir)Microsoft.TestPlatform.Extensions.HtmlLogger\$(Configuration)\netstandard2.0\**\Microsoft.VisualStudio.TestPlatform.Extensions.Html.TestLogger*.dll"> + tools\net8.0\Extensions\%(RecursiveDir)%(Filename)%(Extension) + + <_PortableNetTrx Include="$(ArtifactsBinDir)Microsoft.TestPlatform.Extensions.TrxLogger\$(Configuration)\netstandard2.0\**\Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger*.dll"> + tools\net8.0\Extensions\%(RecursiveDir)%(Filename)%(Extension) + + <_PortableNetTestHostProvider Include="$(ArtifactsBinDir)Microsoft.TestPlatform.TestHostProvider\$(Configuration)\netstandard2.0\**\Microsoft.TestPlatform.TestHostRuntimeProvider*.dll"> + tools\net8.0\Extensions\%(RecursiveDir)%(Filename)%(Extension) + + + + + + + + + <_PortableNetDump Include="$(ArtifactsBinDir)DumpMinitool\$(Configuration)\$(NetFrameworkMinimum)\win7-x64\DumpMinitool.exe;$(ArtifactsBinDir)DumpMinitool\$(Configuration)\$(NetFrameworkMinimum)\win7-x64\DumpMinitool.exe.config;$(ArtifactsBinDir)DumpMinitool.x86\$(Configuration)\$(NetFrameworkMinimum)\win-x86\DumpMinitool.x86.exe;$(ArtifactsBinDir)DumpMinitool.x86\$(Configuration)\$(NetFrameworkMinimum)\win-x86\DumpMinitool.x86.exe.config;$(ArtifactsBinDir)DumpMinitool.arm64\$(Configuration)\$(NetFrameworkMinimum)\win10-arm64\DumpMinitool.arm64.exe"> + tools\net8.0\Extensions\dump\%(Filename)%(Extension) + + <_PortableNetDumpArm64Config Include="$(ArtifactsBinDir)DumpMinitool.arm64\$(Configuration)\$(NetFrameworkMinimum)\win10-arm64\DumpMinitool.arm64.exe.config"> + tools\net8.0\dump\Extensions\dump\DumpMinitool.arm64.exe.config + + + + + + + + + <_PortableNetFrameworkTestHostPublishDir>$(TestPlatformPackagingPublishRoot)testhost\$(NetFrameworkMinimum)\ + + + + <_PortableNetFrameworkTestHostPublishedFile Include="$(_PortableNetFrameworkTestHostPublishDir)**\*" + Exclude="$(_PortableNetFrameworkTestHostPublishDir)**\*.xml;$(_PortableNetFrameworkTestHostPublishDir)System.*.dll" /> + + tools\net8.0\TestHostNetFramework\%(RecursiveDir)%(Filename)%(Extension) + + + + + + + + <_PortableThnfTestHostDefault Include="$(ArtifactsBinDir)testhost\$(Configuration)\net47\win7-x64\testhost.net47.exe;$(ArtifactsBinDir)testhost\$(Configuration)\net47\win7-x64\testhost.net47.exe.config;$(ArtifactsBinDir)testhost\$(Configuration)\net471\win7-x64\testhost.net471.exe;$(ArtifactsBinDir)testhost\$(Configuration)\net471\win7-x64\testhost.net471.exe.config;$(ArtifactsBinDir)testhost\$(Configuration)\net472\win7-x64\testhost.net472.exe;$(ArtifactsBinDir)testhost\$(Configuration)\net472\win7-x64\testhost.net472.exe.config;$(ArtifactsBinDir)testhost\$(Configuration)\net48\win7-x64\testhost.net48.exe;$(ArtifactsBinDir)testhost\$(Configuration)\net48\win7-x64\testhost.net48.exe.config;$(ArtifactsBinDir)testhost\$(Configuration)\net481\win7-x64\testhost.net481.exe;$(ArtifactsBinDir)testhost\$(Configuration)\net481\win7-x64\testhost.net481.exe.config"> + tools\net8.0\TestHostNetFramework\%(Filename)%(Extension) + + <_PortableThnfTestHostX86 Include="$(ArtifactsBinDir)testhost.x86\$(Configuration)\net462\win-x86\testhost.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net462\win-x86\testhost.x86.exe.config;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net47\win-x86\testhost.net47.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net47\win-x86\testhost.net47.x86.exe.config;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net471\win-x86\testhost.net471.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net471\win-x86\testhost.net471.x86.exe.config;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net472\win-x86\testhost.net472.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net472\win-x86\testhost.net472.x86.exe.config;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net48\win-x86\testhost.net48.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net48\win-x86\testhost.net48.x86.exe.config;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net481\win-x86\testhost.net481.x86.exe;$(ArtifactsBinDir)testhost.x86\$(Configuration)\net481\win-x86\testhost.net481.x86.exe.config"> + tools\net8.0\TestHostNetFramework\%(Filename)%(Extension) + + <_PortableThnfTestHostArm64 Include="$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net462\win10-arm64\testhost.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net462\win10-arm64\testhost.arm64.exe.config;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net47\win10-arm64\testhost.net47.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net47\win10-arm64\testhost.net47.arm64.exe.config;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net471\win10-arm64\testhost.net471.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net471\win10-arm64\testhost.net471.arm64.exe.config;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net472\win10-arm64\testhost.net472.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net472\win10-arm64\testhost.net472.arm64.exe.config;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net48\win10-arm64\testhost.net48.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net48\win10-arm64\testhost.net48.arm64.exe.config;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net481\win10-arm64\testhost.net481.arm64.exe;$(ArtifactsBinDir)testhost.arm64\$(Configuration)\net481\win10-arm64\testhost.net481.arm64.exe.config"> + tools\net8.0\TestHostNetFramework\%(Filename)%(Extension) + + <_PortableThnfDataCollector Include="$(ArtifactsBinDir)datacollector\$(Configuration)\net48\win7-x64\datacollector.exe;$(ArtifactsBinDir)datacollector\$(Configuration)\net48\win7-x64\datacollector.exe.config"> + tools\net8.0\TestHostNetFramework\%(Filename)%(Extension) + + + + + diff --git a/src/vstest.console/vstest.console.csproj b/src/vstest.console/vstest.console.csproj index ecb449be1e..79d6b65330 100644 --- a/src/vstest.console/vstest.console.csproj +++ b/src/vstest.console/vstest.console.csproj @@ -45,6 +45,13 @@ true + + + + true + From 375ad598879f8864dc512b8f4fcb9346931a9ca8 Mon Sep 17 00:00:00 2001 From: dotnet bot Date: Thu, 9 Jul 2026 00:20:02 -0700 Subject: [PATCH 36/87] Localized file check-in by OneLocBuild Task: Build definition ID 1222: Build ID 3017537 (#16241) * Localized file check-in by OneLocBuild Task: Build definition ID 1222: Build ID 3017529 * Localized file check-in by OneLocBuild Task: Build definition ID 1222: Build ID 3017537 From a857a5cd6705a7054cf83115947cb6f841fa74dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Thu, 9 Jul 2026 09:23:01 +0200 Subject: [PATCH 37/87] Skip a single bad executor instead of failing all executor loading (#16239) A test executor that can't be instantiated (for example one without a parameterless constructor) used to throw while merging the ITestExecutor and ITestExecutor2 lists. That tore down the whole executor extension manager, so GetExecutorExtensionManager returned null for the entire assembly and every executor in it - including the real one - became unreachable, ending in "Could not find test executor" and 0 tests run. Wrap the per-extension instantiation in MergeTestExtensionLists so one bad executor is skipped rather than failing the whole merge, and apply the same per-item resilience to the LoadAndInitializeAllExtensions loops (executor, discovery, settings) and to the debugger-attach probe in BaseRunTests. Add a regression test with a rogue ITestExecutor2 hosted in TestUtilities. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../TestDiscoveryExtensionManager.cs | 33 ++++-- .../TestExecutorExtensionManager.cs | 47 +++++--- .../SettingsProviderExtensionManager.cs | 20 ++-- .../Execution/BaseRunTests.cs | 30 ++++-- .../TestExecutorExtensionManagerTests.cs | 20 ++++ .../ResilientExecutorFixtures.cs | 102 ++++++++++++++++++ 6 files changed, 213 insertions(+), 39 deletions(-) create mode 100644 test/Microsoft.TestPlatform.TestUtilities/ResilientExecutorFixtures.cs diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestDiscoveryExtensionManager.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestDiscoveryExtensionManager.cs index 885b279e55..1570778cb3 100644 --- a/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestDiscoveryExtensionManager.cs +++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestDiscoveryExtensionManager.cs @@ -99,16 +99,11 @@ public static TestDiscoveryExtensionManager GetDiscoveryExtensionManager(string /// The throw On Error. internal static void LoadAndInitializeAllExtensions(bool throwOnError) { + TestDiscoveryExtensionManager allDiscoverers; + try { - var allDiscoverers = Create(); - - // Iterate throw the discoverers so that they are initialized - foreach (var discoverer in allDiscoverers.Discoverers) - { - // discoverer.value below is what initializes the extension types and hence is not under a EqtTrace.IsVerboseEnabled check. - EqtTrace.Verbose("TestDiscoveryManager: LoadExtensions: Created discoverer {0}", discoverer.Value); - } + allDiscoverers = Create(); } catch (Exception ex) { @@ -118,6 +113,28 @@ internal static void LoadAndInitializeAllExtensions(bool throwOnError) { throw; } + + return; + } + + // Iterate through the discoverers so that they are initialized. One discoverer failing to + // initialize must not prevent the remaining discoverers from being loaded. + foreach (var discoverer in allDiscoverers.Discoverers) + { + try + { + // discoverer.value below is what initializes the extension types and hence is not under a EqtTrace.IsVerboseEnabled check. + EqtTrace.Verbose("TestDiscoveryManager: LoadExtensions: Created discoverer {0}", discoverer.Value); + } + catch (Exception ex) + { + EqtTrace.Error("TestDiscoveryManager: LoadExtensions: Exception occurred while loading extension {0}: {1}", discoverer.TestPluginInfo?.IdentifierData, ex); + + if (throwOnError) + { + throw; + } + } } } diff --git a/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestExecutorExtensionManager.cs b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestExecutorExtensionManager.cs index 1e3c228254..a49b2cede6 100644 --- a/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestExecutorExtensionManager.cs +++ b/src/Microsoft.TestPlatform.Common/ExtensionFramework/TestExecutorExtensionManager.cs @@ -77,11 +77,29 @@ private static IEnumerable> MergeTestExtension // we prefer the second extension to the first. foreach (var testExtension in testExtensions2) { - if (testExtension.TestPluginInfo?.IdentifierData is not null - && cache.ContainsKey(testExtension.TestPluginInfo.IdentifierData)) + if (testExtension.TestPluginInfo?.IdentifierData is null + || !cache.ContainsKey(testExtension.TestPluginInfo.IdentifierData)) { + continue; + } + + try + { + // Accessing Value instantiates the extension. A rogue extension (for example one + // without a parameterless constructor) throws here. Instantiating one bad extension + // must not prevent the remaining extensions from being merged, otherwise the whole + // executor extension manager fails to build and every executor becomes unreachable. + // Skip the failing extension and keep the version already present in the cache from + // the first list. cache[testExtension.TestPluginInfo.IdentifierData] = new(testExtension.Value, testExtension.Metadata); } + catch (Exception ex) + { + EqtTrace.Error( + "TestExecutorExtensionManager: MergeTestExtensionLists: Failed to instantiate extension '{0}'. Skipping it. {1}", + testExtension.TestPluginInfo.IdentifierData, + ex); + } } // Create the merged test extensions list from the cache. @@ -197,24 +215,27 @@ internal static void LoadAndInitializeAllExtensions(bool shouldThrowOnError) { var executorExtensionManager = Create(); - try + foreach (var executor in executorExtensionManager.TestExtensions) { - foreach (var executor in executorExtensionManager.TestExtensions) + try { // Note: - The below Verbose call should not be under IsVerboseEnabled check as we want to // call executor.Value even if logging is not enabled. EqtTrace.Verbose("TestExecutorExtensionManager: Loading executor {0}", executor.Value); } - } - catch (Exception ex) - { - EqtTrace.Error( - "TestExecutorExtensionManager: LoadAndInitialize: Exception occurred while loading extensions {0}", - ex); - - if (shouldThrowOnError) + catch (Exception ex) { - throw; + // Instantiating one executor must not prevent the remaining executors from being + // loaded. Log and move on to the next executor unless the caller opted into failing. + EqtTrace.Error( + "TestExecutorExtensionManager: LoadAndInitialize: Exception occurred while loading extension {0}: {1}", + executor.TestPluginInfo?.IdentifierData, + ex); + + if (shouldThrowOnError) + { + throw; + } } } } diff --git a/src/Microsoft.TestPlatform.Common/SettingsProvider/SettingsProviderExtensionManager.cs b/src/Microsoft.TestPlatform.Common/SettingsProvider/SettingsProviderExtensionManager.cs index 4b0d58fab1..8e39637516 100644 --- a/src/Microsoft.TestPlatform.Common/SettingsProvider/SettingsProviderExtensionManager.cs +++ b/src/Microsoft.TestPlatform.Common/SettingsProvider/SettingsProviderExtensionManager.cs @@ -139,22 +139,24 @@ public static void LoadAndInitializeAllExtensions(bool shouldThrowOnError) { var extensionManager = Create(); - try + foreach (var settingsProvider in extensionManager.SettingsProvidersMap) { - foreach (var settingsProvider in extensionManager.SettingsProvidersMap) + try { // Note: - The below Verbose call should not be under IsVerboseEnabled check as we want to // call executor.Value even if logging is not enabled. EqtTrace.Verbose("SettingsProviderExtensionManager: Loading settings provider {0}", settingsProvider.Value.Value); } - } - catch (Exception ex) - { - EqtTrace.Error("SettingsProviderExtensionManager: LoadAndInitialize: Exception occurred while loading extensions {0}", ex); - - if (shouldThrowOnError) + catch (Exception ex) { - throw; + // Instantiating one settings provider must not prevent the remaining providers from + // being loaded. Log and move on unless the caller opted into failing. + EqtTrace.Error("SettingsProviderExtensionManager: LoadAndInitialize: Exception occurred while loading extension {0}: {1}", settingsProvider.Key, ex); + + if (shouldThrowOnError) + { + throw; + } } } } diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/BaseRunTests.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/BaseRunTests.cs index 552f082dad..6e25f7cc8d 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/BaseRunTests.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Execution/BaseRunTests.cs @@ -411,22 +411,34 @@ private bool RunTestInternalWithExecutors(IEnumerable> execut // host by default. // Same goes if all adapters implement the new test executor interface but at // least one of them needs the test platform to attach to the default test host. - if (executor.Value is not ITestExecutor2 - || ShouldAttachDebuggerToTestHost(executor, executorUriExtensionTuple, RunContext)) + try { - EqtTrace.Verbose("Attaching to default test host."); + if (executor.Value is not ITestExecutor2 + || ShouldAttachDebuggerToTestHost(executor, executorUriExtensionTuple, RunContext)) + { + EqtTrace.Verbose("Attaching to default test host."); - attachedToTestHost = true; + attachedToTestHost = true; #if NET - var pid = Environment.ProcessId; + var pid = Environment.ProcessId; #else - var pid = Process.GetCurrentProcess().Id; + var pid = Process.GetCurrentProcess().Id; #endif - if (!FrameworkHandle.AttachDebuggerToProcess(pid)) - { - EqtTrace.Warning(string.Format(CultureInfo.CurrentCulture, CrossPlatEngineResources.AttachDebuggerToDefaultTestHostFailure, pid)); + if (!FrameworkHandle.AttachDebuggerToProcess(pid)) + { + EqtTrace.Warning(string.Format(CultureInfo.CurrentCulture, CrossPlatEngineResources.AttachDebuggerToDefaultTestHostFailure, pid)); + } } } + catch (Exception ex) + { + // Accessing executor.Value instantiates the executor, which can throw for a rogue + // extension (for example one without a parameterless constructor). A failure to + // evaluate the debugger-attach condition for one executor must not abort processing + // of the remaining executors; the failure is surfaced per-executor in the execution + // loop below. + EqtTrace.Error("BaseRunTests.RunTestInternalWithExecutors: Failed to evaluate debugger attach for executor {0}: {1}", executorUriExtensionTuple.Item1.AbsoluteUri, ex); + } } diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestExecutorExtensionManagerTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestExecutorExtensionManagerTests.cs index 6f0b810351..601b36d3b2 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestExecutorExtensionManagerTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/ExtensionFramework/TestExecutorExtensionManagerTests.cs @@ -52,6 +52,26 @@ public void GetExecutorExtensionManagerShouldReturnAnExecutionManagerWithExtensi Assert.IsTrue(extensionManager.TestExtensions.Any()); } + [TestMethod] + public void GetExecutorExtensionManagerShouldBeResilientToExecutorThatCannotBeInstantiated() + { + // The TestUtilities assembly contains a good ITestExecutor2 and a rogue ITestExecutor2 that + // has no parameterless constructor and therefore throws when instantiated. Building the + // extension manager must not fail because of the rogue executor, and the good executor must + // remain resolvable. + var extensionManager = + TestExecutorExtensionManager.GetExecutionExtensionManager( + typeof(GoodResilientTestExecutor).Assembly.Location); + + Assert.IsNotNull(extensionManager.TestExtensions); + + var discoveredUris = extensionManager.TestExtensions.Select(e => e.Metadata.ExtensionUri).ToList(); + Assert.Contains( + GoodResilientTestExecutor.ExecutorUri, + discoveredUris, + "The good executor should be discovered and merged even though a sibling executor cannot be instantiated."); + } + #region LoadAndInitialize tests [TestMethod] diff --git a/test/Microsoft.TestPlatform.TestUtilities/ResilientExecutorFixtures.cs b/test/Microsoft.TestPlatform.TestUtilities/ResilientExecutorFixtures.cs new file mode 100644 index 0000000000..407d2051fb --- /dev/null +++ b/test/Microsoft.TestPlatform.TestUtilities/ResilientExecutorFixtures.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; + +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; + +namespace Microsoft.TestPlatform.TestUtilities; + +/// +/// Test executors used to verify that the extension managers stay resilient when a single +/// cannot be instantiated. +/// +/// +/// These types intentionally live in this non test assembly so that they are only discovered when a +/// test explicitly points discovery at this assembly (via +/// GetExecutionExtensionManager(<this assembly>)). They are not named +/// *TestAdapter.dll so the default extension discovery does not pick them up, and the unit +/// tests that assert every discovered executor is created point at their own assemblies, so hosting +/// a deliberately broken executor here does not break them. +/// +/// +[ExtensionUri(GoodResilientTestExecutor.ExecutorUri)] +public class GoodResilientTestExecutor : ITestExecutor2 +{ + /// + /// The extension URI of this executor. + /// + public const string ExecutorUri = "executor://resilient.good"; + + public void Cancel() + { + } + + public void RunTests(IEnumerable? tests, IRunContext? runContext, IFrameworkHandle? frameworkHandle) + { + } + + public void RunTests(IEnumerable? sources, IRunContext? runContext, IFrameworkHandle? frameworkHandle) + { + } + + public bool ShouldAttachToTestHost(IEnumerable? sources, IRunContext runContext) + { + return false; + } + + public bool ShouldAttachToTestHost(IEnumerable? tests, IRunContext runContext) + { + return false; + } +} + +/// +/// An that cannot be instantiated because it has no parameterless +/// constructor. Discovery finds this type (discovery does not require a parameterless constructor), +/// but instantiating it throws . This reproduces the "rogue +/// executor" scenario that used to tear down the whole executor extension manager. +/// +[ExtensionUri(RogueResilientTestExecutor.ExecutorUri)] +public class RogueResilientTestExecutor : ITestExecutor2 +{ + /// + /// The extension URI of this executor. + /// + public const string ExecutorUri = "executor://resilient.rogue"; + + /// + /// Initializes a new instance of the class. + /// The required parameter means there is no parameterless constructor, so the extension + /// framework cannot activate this type. + /// + /// A required parameter that prevents parameterless activation. + public RogueResilientTestExecutor(string required) + { + _ = required; + } + + public void Cancel() + { + } + + public void RunTests(IEnumerable? tests, IRunContext? runContext, IFrameworkHandle? frameworkHandle) + { + } + + public void RunTests(IEnumerable? sources, IRunContext? runContext, IFrameworkHandle? frameworkHandle) + { + } + + public bool ShouldAttachToTestHost(IEnumerable? sources, IRunContext runContext) + { + return false; + } + + public bool ShouldAttachToTestHost(IEnumerable? tests, IRunContext runContext) + { + return false; + } +} From a5708878726e8131de33d7d222520ec5da96eb75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Thu, 9 Jul 2026 09:26:11 +0200 Subject: [PATCH 38/87] Inject DataCollectionManager into DataCollectionTestCaseEventHandler (#16242) DataCollectionTestCaseEventHandler read DataCollectionManager.Instance in its convenience constructor, so the datacollector host's request handler and its test-case event handler only agreed on the same manager because both resolved the same static. DataCollectionRequestHandler.Create already has that manager in hand - it is the return value of DataCollectionManager.Create - so I thread that one instance into a new (messageSink, dataCollectionManager) constructor instead of letting the handler re-read the static. The 1-arg constructor stays and still defaults to DataCollectionManager.Instance, so runtime behavior is unchanged and the static is untouched (no [Obsolete]). Everything here is internal, so there is no public API change. Added a test that pins the handler keeps the injected manager rather than reaching for the static, guarding against a future revert to .Instance. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../DataCollectionRequestHandler.cs | 5 +++-- .../DataCollectionTestCaseEventHandler.cs | 11 ++++++++++- ...DataCollectionTestCaseEventHandlerTests.cs | 19 +++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestHandler.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestHandler.cs index 6c47181773..51dd40f053 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestHandler.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionRequestHandler.cs @@ -142,12 +142,13 @@ public static DataCollectionRequestHandler Create( { var requestData = new RequestData(); var telemetryReporter = new TelemetryReporter(requestData, communicationManager, JsonDataSerializer.Instance); + var dataCollectionManager = DataCollectionManager.Create(messageSink, requestData, telemetryReporter); Instance = new DataCollectionRequestHandler( communicationManager, messageSink, - DataCollectionManager.Create(messageSink, requestData, telemetryReporter), - new DataCollectionTestCaseEventHandler(messageSink), + dataCollectionManager, + new DataCollectionTestCaseEventHandler(messageSink, dataCollectionManager), JsonDataSerializer.Instance, new FileHelper(), requestData); diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventHandler.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventHandler.cs index ee8c29c957..4d4614c771 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventHandler.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/DataCollectionTestCaseEventHandler.cs @@ -29,7 +29,16 @@ internal class DataCollectionTestCaseEventHandler : IDataCollectionTestCaseEvent /// Initializes a new instance of the class. /// internal DataCollectionTestCaseEventHandler(IMessageSink messageSink) - : this(messageSink, new SocketCommunicationManager(), DataCollectionManager.Instance, JsonDataSerializer.Instance) + : this(messageSink, DataCollectionManager.Instance) + { } + + /// + /// Initializes a new instance of the class. + /// + /// Sink for messages + /// Data collection manager implementation. + internal DataCollectionTestCaseEventHandler(IMessageSink messageSink, IDataCollectionManager? dataCollectionManager) + : this(messageSink, new SocketCommunicationManager(), dataCollectionManager, JsonDataSerializer.Instance) { } /// diff --git a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs index 005a7fb52e..ddcfa1eb8c 100644 --- a/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs +++ b/test/Microsoft.TestPlatform.CommunicationUtilities.UnitTests/DataCollectionTestCaseEventHandlerTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.ObjectModel; using System.Net; +using System.Reflection; using Microsoft.VisualStudio.TestPlatform.Common.DataCollector.Interfaces; using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; @@ -216,4 +217,22 @@ public void ProcessRequestsShouldThrowExceptionIfThrownByCommunicationManager() Assert.ThrowsExactly(() => _requestHandler.ProcessRequests()); } + + [TestMethod] + public void ConstructorShouldForwardTestCaseEventsToTheInjectedDataCollectionManager() + { + // DataCollectionRequestHandler.Create hands the DataCollectionManager it built to this handler + // through the (messageSink, dataCollectionManager) ctor. Guard that the handler keeps exactly that + // instance to forward test-case events to, instead of reaching back to DataCollectionManager.Instance. + // If a future edit reverted to the static, the writer (the datacollector-host root) and the reader + // (this handler) could drift onto two different managers with nothing turning red. + var injectedManager = new Mock(); + + var requestHandler = new DataCollectionTestCaseEventHandler(_messageSink.Object, injectedManager.Object); + + var managerField = typeof(DataCollectionTestCaseEventHandler) + .GetField("_dataCollectionManager", BindingFlags.Instance | BindingFlags.NonPublic); + Assert.IsNotNull(managerField); + Assert.AreSame(injectedManager.Object, managerField.GetValue(requestHandler)); + } } From 1f9da71a695c60a062644a868b7e52c8d2c19e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Thu, 9 Jul 2026 09:26:36 +0200 Subject: [PATCH 39/87] Inject CommandLineOptions into vstest.console readers (#16243) The argument processors (the writers) were threaded off CommandLineOptions.Instance in earlier phases, but the readers stayed on the static. This converts the two remaining readers so a value a writer sets on an injected CommandLineOptions is observed by the reader through the same instance. TestRequestManager gets an intermediate ctor that derives its defaults from the passed CommandLineOptions (including IsDesignMode for the metrics publisher); the parameterless ctor defaults to CommandLineOptions.Instance, so runtime is unchanged. ConsoleLogger is a reflection-instantiated extension, so it reads (_commandLineOptions ?? CommandLineOptions.Instance) lazily at event time. Adds a same-instance guard test: a --TestCaseFilter writer and TestRequestManager reader share one injected instance and the reader observes the filter, while a manager bound to the static default does not. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/vstest.console/Internal/ConsoleLogger.cs | 12 ++-- .../TestPlatformHelpers/TestRequestManager.cs | 9 ++- .../Internal/ConsoleLoggerTests.cs | 38 ++++++++++ .../TestRequestManagerTests.cs | 69 +++++++++++++++++++ 4 files changed, 122 insertions(+), 6 deletions(-) diff --git a/src/vstest.console/Internal/ConsoleLogger.cs b/src/vstest.console/Internal/ConsoleLogger.cs index 7d6ef0278f..b5d9f2ba1f 100644 --- a/src/vstest.console/Internal/ConsoleLogger.cs +++ b/src/vstest.console/Internal/ConsoleLogger.cs @@ -121,11 +121,12 @@ public ConsoleLogger() /// /// Constructor added for testing purpose /// - internal ConsoleLogger(IOutput output, IProgressIndicator progressIndicator, IFeatureFlag featureFlag) + internal ConsoleLogger(IOutput output, IProgressIndicator progressIndicator, IFeatureFlag featureFlag, CommandLineOptions? commandLineOptions = null) { Output = output; _progressIndicator = progressIndicator; _featureFlag = featureFlag; + _commandLineOptions = commandLineOptions; } /// @@ -142,6 +143,8 @@ protected static IOutput? Output private readonly IFeatureFlag _featureFlag = FeatureFlag.Instance; + private readonly CommandLineOptions? _commandLineOptions; + /// /// Get the verbosity level for the console logger /// @@ -415,10 +418,11 @@ private void TestRunStartHandler(object? sender, TestRunStartEventArgs e) TPDebug.Assert(Output != null, "Initialize should have been called"); // Print all test containers. - Output.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestSourcesDiscovered, CommandLineOptions.Instance.Sources.Count()), OutputLevel.Information); + var commandLineOptions = _commandLineOptions ?? CommandLineOptions.Instance; + Output.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestSourcesDiscovered, commandLineOptions.Sources.Count()), OutputLevel.Information); if (VerbosityLevel == Verbosity.Detailed) { - foreach (var source in CommandLineOptions.Instance.Sources) + foreach (var source in commandLineOptions.Sources) { Output.WriteLine(source, OutputLevel.Information); } @@ -684,7 +688,7 @@ private void TestRunCompleteHandler(object? sender, TestRunCompleteEventArgs e) // DISABLE_ARTIFACTS_POSTPROCESSING_NEW_SDK_UX(new UX) is disabled _featureFlag.IsSet(FeatureFlag.VSTEST_DISABLE_ARTIFACTS_POSTPROCESSING_NEW_SDK_UX) || // TestSessionCorrelationId is null(we're not running through the dotnet SDK). - CommandLineOptions.Instance.TestSessionCorrelationId is null) + (_commandLineOptions ?? CommandLineOptions.Instance).TestSessionCorrelationId is null) { Output.Information(false, CommandLineResources.AttachmentsBanner); TPDebug.Assert(e.AttachmentSets != null, "e.AttachmentSets should not be null when runLevelAttachmentsCount > 0."); diff --git a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs index c4865c381f..053a4d7282 100644 --- a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs +++ b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs @@ -96,15 +96,20 @@ internal class TestRequestManager : ITestRequestManager /// Initializes a new instance of the class. /// public TestRequestManager() + : this(CommandLineOptions.Instance) + { + } + + internal TestRequestManager(CommandLineOptions commandLineOptions) : this( - CommandLineOptions.Instance, + commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, TestPlatformEventSource.Instance, new InferHelper(AssemblyMetadataProvider.Instance), MetricsPublisherFactory.GetMetricsPublisher( IsTelemetryOptedIn(), - CommandLineOptions.Instance.IsDesignMode), + commandLineOptions.IsDesignMode), new ProcessHelper(), new TestRunAttachmentsProcessingManager(TestPlatformEventSource.Instance, new DataCollectorAttachmentsProcessorsFactory()), new PlatformEnvironment(), diff --git a/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs b/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs index 786663f931..2ba642c910 100644 --- a/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs +++ b/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs @@ -1001,6 +1001,44 @@ public void TestRunCompleteHandlerShouldWriteToConsoleIfTestsAbortedWithoutRunni _mockOutput.Verify(o => o.WriteLine(CommandLineResources.TestRunAborted, OutputLevel.Error), Times.Once()); } + [TestMethod] + public void TestRunStartHandlerShouldUseInjectedCommandLineOptionsSourcesRatherThanTheStaticDefault() + { + // The console logger reads the discovered sources from the CommandLineOptions it was given. Injecting a distinct + // instance (with its own source) while the static default stays empty proves the reader resolves to the injected + // instance rather than to CommandLineOptions.Instance. + CommandLineOptions.Reset(); + + var fileHelper = new Mock(); + var injectedOptions = new CommandLineOptions + { + FileHelper = fileHelper.Object, + FilePatternParser = new FilePatternParser(new Mock().Object, fileHelper.Object) + }; + string testFilePath = Path.Combine(Path.GetTempPath(), "InjectedTestFile.dll"); + fileHelper.Setup(fh => fh.Exists(testFilePath)).Returns(true); + injectedOptions.AddSource(testFilePath); + + // The static default carries no sources, so a discovered count of 1 can only come from the injected instance. + Assert.AreEqual(0, CommandLineOptions.Instance.Sources.Count()); + + var consoleLogger = new ConsoleLogger(_mockOutput.Object, _mockProgressIndicator.Object, _mockFeatureFlag.Object, injectedOptions); + + var loggerEvents = new InternalTestLoggerEvents(TestSessionMessageLogger.Instance); + loggerEvents.EnableEvents(); + var parameters = new Dictionary + { + { "verbosity", "normal" } + }; + consoleLogger.Initialize(loggerEvents, parameters); + + var testRunStartEventArgs = new TestRunStartEventArgs(new TestRunCriteria(new List { testFilePath }, 1)); + loggerEvents.RaiseTestRunStart(testRunStartEventArgs); + loggerEvents.WaitForEventCompletion(); + + _mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestSourcesDiscovered, 1), OutputLevel.Information), Times.Once()); + } + [TestMethod] public void TestRunStartHandlerShouldWriteNumberOfTestSourcesDiscoveredOnConsole() { diff --git a/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs b/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs index 70f9e153f2..0749c933e4 100644 --- a/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs +++ b/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs @@ -2603,6 +2603,75 @@ public void WritingIsDefaultTargetArchitectureThroughPlatformArgumentExecutorIsO "with IsDefaultTargetArchitecture flipped to false through the shared helper the manager returns the run configuration's TargetPlatform default and ignores ARM"); } + [TestMethod] + public void WritingTestCaseFilterThroughArgumentExecutorIsObservedByTestRequestManager() + { + // -- Arrange + // The --TestCaseFilter argument executor (the writer) and this TestRequestManager (the reader) are handed the + // same CommandLineOptions instance: _commandLineOptions, which was injected into the manager in the test + // constructor. This guards the same-instance contract of the injection - a value the writer sets on the injected + // options has to be observed by the reader precisely because both ends resolve to one object and not to two + // separate copies. + const string filter = "FullyQualifiedName~SharedInstanceMarker"; + + // The process-wide static default is a different object. Capturing it up front lets us prove the write lands on + // the injected instance only, and that a reader bound to the static default observes none of it. + var staticDefault = CommandLineOptions.Instance; + staticDefault.Should().NotBeSameAs(_commandLineOptions, "the manager under test was injected with a separate CommandLineOptions instance"); + + var payload = new DiscoveryRequestPayload() + { + Sources = new List() { "AnyCPU.dll" }, + RunSettings = DefaultRunsettings + }; + + DiscoveryCriteria? observedCriteria = null; + _mockTestPlatform.Setup(mt => mt.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) + .Callback((IRequestData _, DiscoveryCriteria criteria, TestPlatformOptions _, Dictionary _, IWarningLogger _) => + observedCriteria = criteria) + .Returns(_mockDiscoveryRequest.Object); + + // -- Act + // Writer: parsing "--TestCaseFilter " sets TestCaseFilterValue on the injected instance and nowhere else. + new TestCaseFilterArgumentExecutor(_commandLineOptions).Initialize(filter); + _commandLineOptions.TestCaseFilterValue.Should().Be(filter, "the executor writes the filter on the injected instance"); + staticDefault.TestCaseFilterValue.Should().BeNull("the write must not leak onto the static default instance"); + + // Reader: the manager copies TestCaseFilterValue from the same injected instance onto the DiscoveryCriteria. + _testRequestManager.DiscoverTests(payload, new Mock().Object, _protocolConfig); + + // -- Assert + observedCriteria.Should().NotBeNull(); + observedCriteria!.TestCaseFilter.Should().Be(filter, "the reader observed the writer's value through the shared instance"); + + // Reader-vs-reader: a second manager bound to the static default instance (which never received the write) must + // NOT observe the filter - it falls back to the run settings, which carry none, so the criteria filter is null. + DiscoveryCriteria? staticDefaultCriteria = null; + var mockTestPlatformForStaticDefault = new Mock(); + mockTestPlatformForStaticDefault.Setup(mt => mt.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) + .Callback((IRequestData _, DiscoveryCriteria criteria, TestPlatformOptions _, Dictionary _, IWarningLogger _) => + staticDefaultCriteria = criteria) + .Returns(new Mock().Object); + + var managerBoundToStaticDefault = new TestRequestManager( + staticDefault, + mockTestPlatformForStaticDefault.Object, + new DummyTestRunResultAggregator(), + _mockTestPlatformEventSource.Object, + _inferHelper, + _mockMetricsPublisherTask, + _mockProcessHelper.Object, + _mockAttachmentsProcessingManager.Object, + _mockEnvironment.Object, + _mockEnvironmentVariableHelper.Object, + _runSettingsHelper); + + managerBoundToStaticDefault.DiscoverTests(payload, new Mock().Object, _protocolConfig); + + staticDefaultCriteria.Should().NotBeNull(); + staticDefaultCriteria!.TestCaseFilter.Should().BeNull("the manager bound to the static default instance never saw the write"); + } + [TestMethod] public void UsingInvalidValueForDefaultPlatformSettingThrowsSettingsException() { From 1f3ae477c4350a6d0b139e46c1d87e35c1177185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Thu, 9 Jul 2026 11:01:16 +0200 Subject: [PATCH 40/87] perf: eliminate GetRawText() string allocation in STJ deserializer converters (#16210) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace StjSafe.Deserialize(element.GetRawText(), options) with StjSafe.Deserialize(element, options) across 9 serialization converters in the IPC hot path. JsonElement.GetRawText() allocates a new string (the raw JSON fragment) which is then immediately reparsed by JsonSerializer.Deserialize(string,…). The JsonElement overload of Deserialize reads directly from the element, skipping both the string allocation and the re-parse. Affected converters (all inside #if NETCOREAPP): - AttachmentConverters.cs - TestObjectBaseConverter.cs - ExceptionConverter.cs - TestRunChangedEventArgsConverter.cs - TestCaseConverter.cs - TestRunCompleteEventArgsConverter.cs - TestExecutionContextConverter.cs - AfterTestRunEndResultConverter.cs - DiscoveryCriteriaConverter.cs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Serialization/AfterTestRunEndResultConverter.cs | 2 +- .../Serialization/AttachmentConverters.cs | 2 +- .../Serialization/DiscoveryCriteriaConverter.cs | 2 +- .../Serialization/ExceptionConverter.cs | 2 +- .../Serialization/TestCaseConverter.cs | 2 +- .../Serialization/TestExecutionContextConverter.cs | 2 +- .../Serialization/TestObjectBaseConverter.cs | 2 +- .../Serialization/TestRunChangedEventArgsConverter.cs | 2 +- .../Serialization/TestRunCompleteEventArgsConverter.cs | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/AfterTestRunEndResultConverter.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/AfterTestRunEndResultConverter.cs index 94a531cf33..e51fcb65f5 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/AfterTestRunEndResultConverter.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/AfterTestRunEndResultConverter.cs @@ -52,7 +52,7 @@ public override void Write(Utf8JsonWriter writer, AfterTestRunEndResult value, J { if (element.TryGetProperty(name, out var prop) && prop.ValueKind != JsonValueKind.Null) { - return StjSafe.Deserialize(prop.GetRawText(), options); + return StjSafe.Deserialize(prop, options); } return default; diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/AttachmentConverters.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/AttachmentConverters.cs index 6c58736246..bd1331e88b 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/AttachmentConverters.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/AttachmentConverters.cs @@ -32,7 +32,7 @@ internal class AttachmentSetConverter : JsonConverter { if (attachment.ValueKind != JsonValueKind.Null) { - attachmentSet.Attachments.Add(StjSafe.Deserialize(attachment.GetRawText(), options)!); + attachmentSet.Attachments.Add(StjSafe.Deserialize(attachment, options)!); } } } diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/DiscoveryCriteriaConverter.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/DiscoveryCriteriaConverter.cs index 10ac7493b1..76e20512f1 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/DiscoveryCriteriaConverter.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/DiscoveryCriteriaConverter.cs @@ -75,7 +75,7 @@ public override void Write(Utf8JsonWriter writer, DiscoveryCriteria value, JsonS { if (element.TryGetProperty(name, out var prop) && prop.ValueKind != JsonValueKind.Null) { - return StjSafe.Deserialize(prop.GetRawText(), options); + return StjSafe.Deserialize(prop, options); } return default; diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/ExceptionConverter.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/ExceptionConverter.cs index ecc63f2b61..d9494ae3f4 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/ExceptionConverter.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/ExceptionConverter.cs @@ -49,7 +49,7 @@ internal class ExceptionConverter : JsonConverter Exception? innerException = null; if (root.TryGetProperty("InnerException", out var innerProp) && innerProp.ValueKind != JsonValueKind.Null) { - innerException = StjSafe.Deserialize(innerProp.GetRawText(), options); + innerException = StjSafe.Deserialize(innerProp, options); } var exception = new RemoteException(className, message, stackTrace, innerException); diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestCaseConverter.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestCaseConverter.cs index 7f2f49ad0f..821dd1381e 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestCaseConverter.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestCaseConverter.cs @@ -39,7 +39,7 @@ internal class TestCaseConverter : JsonConverter return null; } - var testProperty = StjSafe.Deserialize(keyElement.GetRawText(), options); + var testProperty = StjSafe.Deserialize(keyElement, options); if (testProperty is null) { diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestExecutionContextConverter.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestExecutionContextConverter.cs index c9e3caf7a3..60e957a7b5 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestExecutionContextConverter.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestExecutionContextConverter.cs @@ -40,7 +40,7 @@ internal class TestExecutionContextConverter : JsonConverter(filterOptions.GetRawText(), options); + context.FilterOptions = StjSafe.Deserialize(filterOptions, options); return context; } diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestObjectBaseConverter.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestObjectBaseConverter.cs index 5d3ba1cc21..fdf9372e2c 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestObjectBaseConverter.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestObjectBaseConverter.cs @@ -70,7 +70,7 @@ public override bool CanConvert(Type typeToConvert) if (!prop.TryGetProperty("Key", out var keyElement)) continue; - var testProperty = StjSafe.Deserialize(keyElement.GetRawText(), options); + var testProperty = StjSafe.Deserialize(keyElement, options); if (testProperty is null) continue; diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestRunChangedEventArgsConverter.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestRunChangedEventArgsConverter.cs index c88842fdcb..e784301831 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestRunChangedEventArgsConverter.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestRunChangedEventArgsConverter.cs @@ -51,7 +51,7 @@ public override void Write(Utf8JsonWriter writer, TestRunChangedEventArgs value, { if (element.TryGetProperty(name, out var prop) && prop.ValueKind != JsonValueKind.Null) { - return StjSafe.Deserialize(prop.GetRawText(), options); + return StjSafe.Deserialize(prop, options); } return default; diff --git a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestRunCompleteEventArgsConverter.cs b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestRunCompleteEventArgsConverter.cs index ad3674a620..6bf482f756 100644 --- a/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestRunCompleteEventArgsConverter.cs +++ b/src/Microsoft.TestPlatform.CommunicationUtilities/Serialization/TestRunCompleteEventArgsConverter.cs @@ -66,7 +66,7 @@ public override void Write(Utf8JsonWriter writer, TestRunCompleteEventArgs value { if (element.TryGetProperty(name, out var prop) && prop.ValueKind != JsonValueKind.Null) { - return StjSafe.Deserialize(prop.GetRawText(), options); + return StjSafe.Deserialize(prop, options); } return default; From 12d7c9c244a93089b82dffd8dba823c29a3780d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Thu, 9 Jul 2026 11:48:43 +0200 Subject: [PATCH 41/87] Inject TestRunResultAggregator into vstest.console Executor (#16245) Executor read TestRunResultAggregator.Instance directly at the end of Execute to fold the run outcome into the exit code, so a test could not observe the reader against an injected aggregator. Thread one instance from the composition root (defaulting to .Instance at the convenience ctors) and read it through the field. Production is unchanged - the 1-arg root funnels through the 4-arg ctor which still defaults to the static. Added a same-instance guard: marking an injected aggregator failed is observed by Executor's exit code through that instance, while the static default stays Passed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/vstest.console/CommandLine/Executor.cs | 10 ++-- .../ExecutorUnitTests.cs | 52 +++++++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/src/vstest.console/CommandLine/Executor.cs b/src/vstest.console/CommandLine/Executor.cs index 4315cfea85..0d7b24453e 100644 --- a/src/vstest.console/CommandLine/Executor.cs +++ b/src/vstest.console/CommandLine/Executor.cs @@ -66,6 +66,7 @@ internal class Executor private readonly IRunSettingsProvider _runSettingsProvider; private readonly IRunSettingsHelper _runSettingsHelper; private readonly CommandLineOptions _commandLineOptions; + private readonly TestRunResultAggregator _testRunResultAggregator; // Left null in production so the argument processors resolve TestRequestManager.Instance lazily // (only when a run/discovery command actually executes); tests inject a specific instance. private readonly ITestRequestManager? _testRequestManager; @@ -99,16 +100,16 @@ internal class Executor } internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSource, IProcessHelper processHelper, IEnvironment environment) - : this(output, testPlatformEventSource, processHelper, environment, RunSettingsManager.Instance, RunSettingsHelper.Instance, CommandLineOptions.Instance) + : this(output, testPlatformEventSource, processHelper, environment, RunSettingsManager.Instance, RunSettingsHelper.Instance, CommandLineOptions.Instance, TestRunResultAggregator.Instance) { } internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSource, IProcessHelper processHelper, IEnvironment environment, IRunSettingsProvider runSettingsProvider) - : this(output, testPlatformEventSource, processHelper, environment, runSettingsProvider, RunSettingsHelper.Instance, CommandLineOptions.Instance) + : this(output, testPlatformEventSource, processHelper, environment, runSettingsProvider, RunSettingsHelper.Instance, CommandLineOptions.Instance, TestRunResultAggregator.Instance) { } - internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSource, IProcessHelper processHelper, IEnvironment environment, IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper, CommandLineOptions commandLineOptions, ITestRequestManager? testRequestManager = null) + internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSource, IProcessHelper processHelper, IEnvironment environment, IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper, CommandLineOptions commandLineOptions, TestRunResultAggregator testRunResultAggregator, ITestRequestManager? testRequestManager = null) { DebuggerBreakpoint.AttachVisualStudioDebugger(WellKnownDebugEnvironmentVariables.VSTEST_RUNNER_DEBUG_ATTACHVS); DebuggerBreakpoint.WaitForNativeDebugger(WellKnownDebugEnvironmentVariables.VSTEST_RUNNER_NATIVE_DEBUG); @@ -122,6 +123,7 @@ internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSour _runSettingsProvider = runSettingsProvider; _runSettingsHelper = runSettingsHelper; _commandLineOptions = commandLineOptions; + _testRunResultAggregator = testRunResultAggregator; _testRequestManager = testRequestManager; } @@ -219,7 +221,7 @@ internal int Execute(params string[]? args) } // Use the test run result aggregator to update the exit code. - exitCode |= (TestRunResultAggregator.Instance.Outcome == TestOutcome.Passed) ? 0 : 1; + exitCode |= (_testRunResultAggregator.Outcome == TestOutcome.Passed) ? 0 : 1; EqtTrace.Verbose("Executor.Execute: Exiting with exit code of {0}", exitCode); diff --git a/test/vstest.console.UnitTests/ExecutorUnitTests.cs b/test/vstest.console.UnitTests/ExecutorUnitTests.cs index c659fb07ed..2f5c543c1b 100644 --- a/test/vstest.console.UnitTests/ExecutorUnitTests.cs +++ b/test/vstest.console.UnitTests/ExecutorUnitTests.cs @@ -15,10 +15,13 @@ using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; using Microsoft.VisualStudio.TestPlatform.Utilities; +using Microsoft.VisualStudio.TestPlatform.Utilities.Helpers; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using vstest.console.UnitTests.TestDoubles; + using CommandLineResources = Microsoft.VisualStudio.TestPlatform.CommandLine.Resources.Resources; namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests; @@ -358,6 +361,55 @@ public void ExecutorShouldPrintRunnerArchitecture() Assert.DoesNotContain(message => message.Message!.Contains("vstest.console.exe is running in emulated mode"), mockOutput.Messages); } + [TestMethod] + public void MarkingTestRunFailedOnInjectedAggregatorIsObservedByExecutorExitCode() + { + // The exit code produced at the end of Executor.Execute is OR-ed with the outcome of the + // TestRunResultAggregator (Executor.cs: exitCode |= (Outcome == Passed) ? 0 : 1). This test + // proves the reader (Executor) observes the SAME aggregator instance it was constructed with, + // not the process-wide TestRunResultAggregator.Instance static. + // + // "--help" is a zero-baseline path: HelpArgumentProcessor runs first and returns Abort, which + // does not set the exit bit (only Fail does), so the aggregator's outcome is the sole + // contributor to the final exit code. That makes the two outcomes below decisively distinct. + + // Baseline the shared static so the negative control is deterministic. + TestRunResultAggregator.Instance.Reset(); + + // Writer: mark a failure on an injected aggregator that is a different instance from the static. + var injectedAggregator = new DummyTestRunResultAggregator(); + injectedAggregator.MarkTestRunFailed(); + Assert.AreNotSame(TestRunResultAggregator.Instance, injectedAggregator); + + // Reader observes the write through the injected instance: Failed outcome sets the exit bit. + var exitCodeWithInjected = new Executor( + new MockOutput(), + _mockTestPlatformEventSource.Object, + new ProcessHelper(), + new PlatformEnvironment(), + RunSettingsManager.Instance, + RunSettingsHelper.Instance, + CommandLineOptions.Instance, + injectedAggregator).Execute("--help"); + + Assert.AreEqual(1, exitCodeWithInjected, "Executor must observe the injected aggregator's Failed outcome."); + + // Negative control: an Executor bound to the static default (still Passed) yields a zero exit + // for the same args, and the write above did not leak onto the static instance. + var exitCodeWithStatic = new Executor( + new MockOutput(), + _mockTestPlatformEventSource.Object, + new ProcessHelper(), + new PlatformEnvironment(), + RunSettingsManager.Instance, + RunSettingsHelper.Instance, + CommandLineOptions.Instance, + TestRunResultAggregator.Instance).Execute("--help"); + + Assert.AreEqual(0, exitCodeWithStatic, "The static default aggregator is still Passed, so its Executor must not set the failure bit."); + Assert.AreEqual(TestOutcome.Passed, TestRunResultAggregator.Instance.Outcome, "Marking the injected aggregator failed must not leak onto the static instance."); + } + private class MockOutput : IOutput { public List Messages { get; set; } = new List(); From 44d581bed09d27cf360e3583bf604373ef55dbbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Thu, 9 Jul 2026 15:07:20 +0200 Subject: [PATCH 42/87] Source Microsoft.TestPlatform.Internal.Uwp payload from each producer's own build (#16247) Repoint the bundled netstandard2.0 assemblies and the ObjectModel/CoreUtilities satellites from this package's commingled $(OutputPath) (where every ProjectReference copies its output) to each producing project's own netstandard2.0 build output, so each bundled DLL is a self-consistent build rather than a file that won a race in the shared folder. Same publish-once/ glob-many pattern as #16206, #16236, #16240 and #16246. Produced package file set is unchanged (+0/-0 vs baseline). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...Microsoft.TestPlatform.Internal.Uwp.csproj | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/package/Microsoft.TestPlatform.Internal.Uwp/Microsoft.TestPlatform.Internal.Uwp.csproj b/src/package/Microsoft.TestPlatform.Internal.Uwp/Microsoft.TestPlatform.Internal.Uwp.csproj index e2ca981baf..9e818cb745 100644 --- a/src/package/Microsoft.TestPlatform.Internal.Uwp/Microsoft.TestPlatform.Internal.Uwp.csproj +++ b/src/package/Microsoft.TestPlatform.Internal.Uwp/Microsoft.TestPlatform.Internal.Uwp.csproj @@ -30,20 +30,25 @@ - + - - - - - - - - - - + + + + + + + + + + From 219bc780717ad1544dd439b96be62de8078954db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Thu, 9 Jul 2026 15:09:55 +0200 Subject: [PATCH 43/87] Source Microsoft.TestPlatform.TestHost payload from testhost's own publish (#16246) Repoint the net8.0 lib/ and build/ content from this package's commingled $(OutputPath) (where every ProjectReference copies its output) to src\testhost's own net8.0 publish output, so each bundled DLL is the exact, self-consistent net8.0 build instead of a file that won a race in the shared folder. The x86 launcher comes from testhost.x86's own build output. msdia natives (external) and the package's own props/targets stay hand-listed. Same publish-once/glob-many pattern as #16206, #16236 and #16240. Produced package file set is unchanged (+0/-0 vs baseline). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Microsoft.TestPlatform.TestHost.csproj | 60 ++++++++++++------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/src/package/Microsoft.TestPlatform.TestHost/Microsoft.TestPlatform.TestHost.csproj b/src/package/Microsoft.TestPlatform.TestHost/Microsoft.TestPlatform.TestHost.csproj index dbc8bb47a7..9d686041ed 100644 --- a/src/package/Microsoft.TestPlatform.TestHost/Microsoft.TestPlatform.TestHost.csproj +++ b/src/package/Microsoft.TestPlatform.TestHost/Microsoft.TestPlatform.TestHost.csproj @@ -75,40 +75,58 @@ - + + + + <_NetTestHostPublishDir>$(TestPlatformPackagingPublishRoot)testhost\$(NetCoreAppMinimum)\ + <_NetTestHostX86Dir>$(ArtifactsBinDir)testhost.x86\$(Configuration)\$(NetCoreAppMinimum)\win-x86\ + + - - - - - + + + + + - + - - - - - + + + + + - + - - - - + + + + From a32abe5ddbff003d3e957434a05e6a4eb29a4898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Thu, 9 Jul 2026 15:10:22 +0200 Subject: [PATCH 44/87] Source bundled sibling DLLs from producer output in ObjectModel package (#16250) Microsoft.TestPlatform.ObjectModel bundles the CoreUtilities and PlatformAbstractions assemblies (plus their satellites) because they are not shipped as standalone NuGet packages. These were cherry-picked out of this project's own commingled $(OutputPath), which mixes the outputs of every referenced project. Point each bundled assembly at its own producing project's build output for the matching target framework instead, so every packaged DLL is exactly the build its owning project produced. CoreUtilities and PlatformAbstractions multi-target the same framework set as ObjectModel, so $(TargetFramework) maps one-to-one to each sibling's own output folder. This is a strict no-op: the produced package file set is byte-for-byte identical (+0/-0) and no DLL target framework is reclassified. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Microsoft.TestPlatform.ObjectModel.csproj | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj b/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj index a83616c556..827ac7665a 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj +++ b/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj @@ -23,14 +23,20 @@ - + - - + + - - + + From 018cbb6586404d2d27da7e3a7bdadedf429b5efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Thu, 9 Jul 2026 15:11:06 +0200 Subject: [PATCH 45/87] Source bundled sibling DLLs from producer output in TranslationLayer package (#16251) Microsoft.TestPlatform.TranslationLayer bundles the Common and CommunicationUtilities assemblies (plus Common/CommunicationUtilities/CoreUtilities satellite resources) because they are not shipped as standalone NuGet packages. These were cherry-picked out of this project's own commingled $(OutputPath), which mixes the outputs of every referenced project. Point each bundled assembly at its own producing project's build output for the matching target framework instead, so every packaged DLL is exactly the build its owning project produced. CommunicationUtilities and CoreUtilities multi-target the same framework set as TranslationLayer, so $(TargetFramework) maps one-to-one to their own output. Common only builds net462 and netstandard2.0, so for .NET (Core) folders such as net8.0 the nearest-compatible netstandard2.0 flavor is used - exactly the one a Common ProjectReference resolved into $(OutputPath) before this change. This is a strict no-op: the produced package file set is byte-for-byte identical (+0/-0), binding redirects still match their DLL versions, and no DLL target framework is reclassified. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...form.VsTestConsole.TranslationLayer.csproj | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.csproj b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.csproj index 29624b0f82..d8876719c5 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.csproj +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.csproj @@ -43,17 +43,31 @@ - + + + + <_TranslationLayerCommonTfm>$(TargetFramework) + <_TranslationLayerCommonTfm Condition="!Exists('$(ArtifactsBinDir)Microsoft.TestPlatform.Common\$(Configuration)\$(TargetFramework)\')">netstandard2.0 + - + - - + + - - - + + + From 98926aa704344566e7e344a64579a3808234904d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Thu, 9 Jul 2026 15:25:23 +0200 Subject: [PATCH 46/87] Revert "Source bundled sibling DLLs from producer output in ObjectModel package" (#16252) This reverts the packaging change from #16250. The "source each payload from its producer's own output" pattern earns its keep only when many projects with different target frameworks publish into one shared folder, so a single filename can be captured in the wrong framework flavor (the .dll / .config binding-redirect split fixed for the VS bundle, CLI, and Portable packages). Microsoft.TestPlatform.ObjectModel is an ordinary multi-targeting library: its $(OutputPath) is per-TFM (bin//net8.0/, .../net462/, .../netstandard2.0/), so the sibling CoreUtilities/PlatformAbstractions DLL that sits in each folder is already the single correct flavor for that framework - there is no commingling to protect against. Reverting restores the shorter, clearer $(OutputPath) form. The conversion was a strict no-op (the package file set was +0/-0 with no DLL reclassified), so this revert is equally a no-op: same files, same flavors, less indirection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Microsoft.TestPlatform.ObjectModel.csproj | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj b/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj index 827ac7665a..a83616c556 100644 --- a/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj +++ b/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj @@ -23,20 +23,14 @@ - + - - + + - - + + From d3c84e5ad7ae2a621f65d1926af37eabc2b96e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Thu, 9 Jul 2026 15:25:34 +0200 Subject: [PATCH 47/87] Revert "Source Microsoft.TestPlatform.Internal.Uwp payload from each producer's own build" (#16254) This reverts the packaging change from #16247. The "source each payload from its producer's own output" pattern earns its keep only when many projects with different target frameworks publish into one shared folder, so a single filename can be captured in the wrong framework flavor (the .dll / .config binding-redirect split fixed for the VS bundle, CLI, and Portable packages). Microsoft.TestPlatform.Internal.Uwp is a single-target (netstandard2.0) package, so every bundled sibling resolves to exactly one flavor - there is no commingling to protect against. Reverting restores the shorter $(OutputPath) form. The conversion was a strict no-op (+0/-0, no DLL reclassified), so this revert is equally a no-op: same files, same flavors, less indirection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...Microsoft.TestPlatform.Internal.Uwp.csproj | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/src/package/Microsoft.TestPlatform.Internal.Uwp/Microsoft.TestPlatform.Internal.Uwp.csproj b/src/package/Microsoft.TestPlatform.Internal.Uwp/Microsoft.TestPlatform.Internal.Uwp.csproj index 9e818cb745..e2ca981baf 100644 --- a/src/package/Microsoft.TestPlatform.Internal.Uwp/Microsoft.TestPlatform.Internal.Uwp.csproj +++ b/src/package/Microsoft.TestPlatform.Internal.Uwp/Microsoft.TestPlatform.Internal.Uwp.csproj @@ -30,25 +30,20 @@ - + - - - - - - - - - - + + + + + + + + + + From f0efb56342389d2551346b2c1c9c6cb491b5b12d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Thu, 9 Jul 2026 15:26:58 +0200 Subject: [PATCH 48/87] Inject built-in ConsoleLogger from the composition root instead of reflection-activating it (#16248) * Inject built-in ConsoleLogger from the composition root instead of reflection-activating it ConsoleLogger reached for CommandLineOptions.Instance because it was reflection-activated through the logger manager and had nowhere to get the parsed options from. Give the composition root (TestRequestManager) a closed internal factory on RequestData that hands the logger manager a pre-built instance for our own built-in extensions, keyed by extension URI. ConsoleLogger now takes CommandLineOptions by injection and its parameterless constructor is gone. Third-party loggers are untouched: the factory returns null for anything it does not know and they fall through to reflection exactly as before. The seam is internal and deliberately not on IRequestData, so it never becomes an extension point. ConsoleLogger is registered in run settings by assembly-qualified name (AddConsoleLogger), not discovered through the extension manager, so the seam sits in InitializeLoggerByType keyed off the resolved type's [ExtensionUri]; the by-URI path is covered too. --ListLoggers now names loggers from discovery metadata instead of constructing them. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Add acceptance test for explicit /logger:console activation The regression that removing ConsoleLogger's parameterless constructor could reintroduce only shows up end-to-end on the explicit /logger:console path, which is registered in run settings by assembly-qualified name with no URI. The unit tests cover the seam but not the real activation through vstest.console. This runs a real assembly with /logger:console and asserts the summary the console logger prints, on both the net481 and netcore runners. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../RequestData.cs | 10 ++ .../Client/TestLoggerManager.cs | 53 +++++++++ src/vstest.console/Internal/ConsoleLogger.cs | 9 +- .../ListExtensionsArgumentProcessor.cs | 10 +- .../TestPlatformHelpers/TestRequestManager.cs | 14 ++- .../ExecutionTests.cs | 24 ++++ .../TestLoggerManagerTests.cs | 108 ++++++++++++++++++ 7 files changed, 224 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.TestPlatform.Common/RequestData.cs b/src/Microsoft.TestPlatform.Common/RequestData.cs index 86f251f55f..c80abdc2de 100644 --- a/src/Microsoft.TestPlatform.Common/RequestData.cs +++ b/src/Microsoft.TestPlatform.Common/RequestData.cs @@ -55,4 +55,14 @@ public ProtocolConfig? ProtocolConfig /// Gets or sets a value indicating whether is telemetry opted in. /// public bool IsTelemetryOptedIn { get; set; } + + /// + /// Gets or sets an optional factory the composition root uses to supply pre-configured instances + /// of its own built-in extensions (keyed by extension URI) instead of reflection-activating them. + /// Returns for unknown / third-party extensions, which continue to be + /// created by reflection. This is an internal, closed injection seam for our own extensions and is + /// deliberately not part of the public contract, so it never becomes a + /// third-party extension point. + /// + internal Func? KnownExtensionInstanceFactory { get; set; } } diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/TestLoggerManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/TestLoggerManager.cs index 232a31145e..6c6f23aba8 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/TestLoggerManager.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/TestLoggerManager.cs @@ -9,6 +9,7 @@ using System.Reflection; using System.Xml; +using Microsoft.VisualStudio.TestPlatform.Common; using Microsoft.VisualStudio.TestPlatform.Common.Exceptions; using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; using Microsoft.VisualStudio.TestPlatform.Common.Logging; @@ -354,6 +355,17 @@ internal bool InitializeLoggerByUri(Uri uri, Dictionary? parame ValidateArg.NotNull(uri, nameof(uri)); CheckDisposed(); + // Give the composition root the first chance to supply a pre-configured instance of its own + // built-in logger (keyed by URI) so it receives injected dependencies instead of a + // reflection-activated one. This covers loggers the user names explicitly in run settings by URI + // or by friendly name (which is resolved to a URI before we get here). Unknown / third-party + // loggers return null and fall through to the reflection-based extension manager below, exactly + // as before, so this does not widen any public extension point. + if ((_requestData as RequestData)?.KnownExtensionInstanceFactory?.Invoke(uri) is ITestLogger knownLogger) + { + return InitializeKnownLogger(knownLogger, uri, parameters); + } + // Look up the extension and initialize it if one is found. var extensionManager = TestLoggerExtensionManager; var logger = extensionManager.TryGetTestExtension(uri.AbsoluteUri); @@ -541,6 +553,23 @@ private bool InitializeLoggerByType(string assemblyQualifiedName, string codeBas assembly?.GetTypes() .FirstOrDefault(x => string.Equals(x.AssemblyQualifiedName, assemblyQualifiedName)); + // Give the composition root (vstest.console) the first chance to supply a pre-configured + // instance of one of its own built-in loggers (for example ConsoleLogger with the parsed + // CommandLineOptions injected). This is the path our shipped ConsoleLogger actually takes: + // it is registered in run settings by assembly-qualified name (see AddConsoleLogger), not + // discovered through the logger extension manager, so it is always activated here. The + // instance is looked up by the extension URI declared on the resolved type, letting our + // extensions receive their dependencies by injection instead of reaching for process-wide + // singletons. Unknown / third-party loggers are not matched by the factory and fall through + // to reflection activation below, exactly as before, so this does not widen any public + // extension point. + if (loggerType?.GetCustomAttribute()?.ExtensionUri is { } extensionUri + && Uri.TryCreate(extensionUri, UriKind.Absolute, out var loggerUri) + && (_requestData as RequestData)?.KnownExtensionInstanceFactory?.Invoke(loggerUri) is ITestLogger knownLogger) + { + return InitializeKnownLogger(knownLogger, loggerUri, parameters); + } + // Create logger instance var constructorInfo = loggerType?.GetConstructor(Type.EmptyTypes); var logger = constructorInfo?.Invoke([]); @@ -577,6 +606,30 @@ private bool InitializeLoggerByType(string assemblyQualifiedName, string codeBas } } + /// + /// Initializes a logger instance supplied directly by the composition root (see + /// ) rather than one created by reflection. + /// Mirrors the dedup / initialize / track steps used by the reflection-based paths. + /// + private bool InitializeKnownLogger(ITestLogger logger, Uri uri, Dictionary? parameters) + { + // If the logger has already been initialized just return. + if (_initializedLoggers.Contains(logger.GetType())) + { + EqtTrace.Verbose("TestLoggerManager: Skipping duplicate logger initialization: {0}", logger.GetType()); + return true; + } + + var initialized = InitializeLogger(logger, uri.AbsoluteUri, parameters); + + if (initialized) + { + _initializedLoggers.Add(logger.GetType()); + } + + return initialized; + } + private bool InitializeLogger(object? logger, string? extensionUri, Dictionary? parameters) { if (logger == null) diff --git a/src/vstest.console/Internal/ConsoleLogger.cs b/src/vstest.console/Internal/ConsoleLogger.cs index b5d9f2ba1f..5c5096f135 100644 --- a/src/vstest.console/Internal/ConsoleLogger.cs +++ b/src/vstest.console/Internal/ConsoleLogger.cs @@ -112,10 +112,15 @@ internal enum Verbosity private string? _targetFramework; /// - /// Default constructor. + /// Constructor. The built-in console logger is activated directly by the composition root (see + /// ) rather than by reflection, so it receives + /// the parsed by injection instead of reaching for the process-wide + /// singleton. and the progress indicator are established later, in + /// . /// - public ConsoleLogger() + internal ConsoleLogger(CommandLineOptions commandLineOptions) { + _commandLineOptions = commandLineOptions; } /// diff --git a/src/vstest.console/Processors/ListExtensionsArgumentProcessor.cs b/src/vstest.console/Processors/ListExtensionsArgumentProcessor.cs index 7ba694b34f..0c9b8ee48d 100644 --- a/src/vstest.console/Processors/ListExtensionsArgumentProcessor.cs +++ b/src/vstest.console/Processors/ListExtensionsArgumentProcessor.cs @@ -160,7 +160,15 @@ public ArgumentProcessorResult Execute() var extensionManager = TestLoggerExtensionManager.Create(new NullMessageLogger()); foreach (var extension in extensionManager.TestExtensions) { - ConsoleOutput.Instance.WriteLine(extension.Value.GetType().FullName, OutputLevel.Information); + // Report the logger's type name from its discovery metadata instead of instantiating the + // extension. Our built-in ConsoleLogger is activated by injection and no longer exposes a + // parameterless constructor, so forcing extension.Value here would fail; and naming the + // available loggers should never require constructing them. + var loggerTypeName = extension.TestPluginInfo?.AssemblyQualifiedName is { } assemblyQualifiedName + ? assemblyQualifiedName.Split(',')[0] + : extension.Value.GetType().FullName; + + ConsoleOutput.Instance.WriteLine(loggerTypeName, OutputLevel.Information); ConsoleOutput.Instance.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.AvailableExtensionsMetadataFormat, "Uri", extension.Metadata.ExtensionUri), OutputLevel.Information); ConsoleOutput.Instance.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.AvailableExtensionsMetadataFormat, "FriendlyName", string.Join(", ", extension.Metadata.FriendlyName)), OutputLevel.Information); } diff --git a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs index 053a4d7282..7685c57683 100644 --- a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs +++ b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs @@ -1421,10 +1421,22 @@ private IRequestData GetRequestData(ProtocolConfig protocolConfig) _telemetryOptedIn || IsTelemetryOptedIn() ? new MetricsCollection() : new NoOpMetricsCollection(), - IsTelemetryOptedIn = _telemetryOptedIn || IsTelemetryOptedIn() + IsTelemetryOptedIn = _telemetryOptedIn || IsTelemetryOptedIn(), + KnownExtensionInstanceFactory = CreateKnownExtensionInstance, }; } + /// + /// Supplies pre-configured instances of vstest.console's own built-in extensions so they receive + /// injected dependencies (here: the parsed ) instead of reaching + /// for process-wide singletons. Unknown extension URIs return and are + /// reflection-activated as before, so this does not widen any public extension point. + /// + private object? CreateKnownExtensionInstance(Uri extensionUri) + => string.Equals(extensionUri.AbsoluteUri, ConsoleLogger.ExtensionUri, StringComparison.OrdinalIgnoreCase) + ? new ConsoleLogger(_commandLineOptions) + : null; + private static List GetSources(TestRunRequestPayload testRunRequestPayload) { // TODO: This should also use hashset to only return distinct sources. diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ExecutionTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ExecutionTests.cs index 41b8daa30a..795e0830cb 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ExecutionTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/ExecutionTests.cs @@ -387,6 +387,30 @@ public void ExecuteTestsShouldSucceedWhenAtLeastOneDllFindsRuntimeProvider(Runne ExitCodeEquals(1); } + [TestMethod] + [TestMatrix(testHost: Net)] + public void ExplicitConsoleLoggerActivatesWhenRequestedByName(RunnerInfo runnerInfo) + { + // The built-in console logger is activated by the composition root (TestRequestManager) handing + // over a pre-built instance with the parsed CommandLineOptions injected, instead of being + // reflection-activated, and it no longer has a parameterless constructor. When the user asks for + // it explicitly with /logger:console it is registered in run settings by assembly-qualified name + // (UpdateConsoleLoggerIfExists) with no URI, so it goes through the assembly-qualified-name + // activation path. This is the path that broke when the parameterless constructor was removed, so + // this test guards that regression end-to-end. The pass/fail/skip summary asserted below is + // printed by the console logger itself, so a passing assertion proves it activated and ran. + SetTestEnvironment(_testEnvironment, runnerInfo); + + var testDll = GetAssetFullPath("MSTestProject1.dll"); + + var arguments = PrepareArguments(testDll, GetTestAdapterPath(), string.Empty, framework: string.Empty, _testEnvironment.InIsolationValue, resultsDirectory: TempDirectory.Path); + arguments = string.Concat(arguments, " /logger:\"console;verbosity=normal\""); + InvokeVsTest(arguments); + + ValidateSummaryStatus(1, 1, 1); + ExitCodeEquals(1); // failing test in MSTestProject1 + } + [TestMethod] // This is a built-in assembly filter test. It changes with vstest.version, so testing against 1 version of console is enough. [TestMatrix(console: Net, testHost: Net)] diff --git a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestLoggerManagerTests.cs b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestLoggerManagerTests.cs index 128616b323..9dc927ff51 100644 --- a/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestLoggerManagerTests.cs +++ b/test/Microsoft.TestPlatform.CrossPlatEngine.UnitTests/TestLoggerManagerTests.cs @@ -7,6 +7,7 @@ using System.Threading; using Microsoft.TestPlatform.TestUtilities; +using Microsoft.VisualStudio.TestPlatform.Common; using Microsoft.VisualStudio.TestPlatform.Common.Exceptions; using Microsoft.VisualStudio.TestPlatform.Common.Logging; using Microsoft.VisualStudio.TestPlatform.Common.Telemetry; @@ -268,6 +269,113 @@ public void AddLoggerShouldNotThrowExceptionIfUriIsNonExistent() Assert.IsFalse(testLoggerManager.InitializeLoggerByUri(new Uri("logger://NotALogger"), null)); } + [TestMethod] + public void InitializeLoggerByUriShouldUseTheInstanceSuppliedByTheCompositionRootForAKnownLogger() + { + // The composition root (vstest.console) can hand a pre-configured instance of one of its own + // built-in loggers (for example ConsoleLogger with the parsed CommandLineOptions injected) to + // the logger manager through RequestData. When it does, that exact instance must be used and + // initialized instead of the logger being reflection-activated, which is how our shipped + // extensions get their dependencies by injection rather than reaching for process-wide singletons. + var injectedLogger = new Mock(); + var requestData = new RequestData + { + KnownExtensionInstanceFactory = uri => + uri.AbsoluteUri == new Uri(_loggerUri).AbsoluteUri ? injectedLogger.Object : null, + }; + var testLoggerManager = new DummyTestLoggerManager(requestData); + + var initialized = testLoggerManager.InitializeLoggerByUri(new Uri(_loggerUri), new()); + + Assert.IsTrue(initialized); + injectedLogger.Verify(l => l.Initialize(It.IsAny(), It.IsAny()), Times.Once); + } + + [TestMethod] + public void InitializeShouldUseTheCompositionRootInstanceForALoggerEnabledByFriendlyNameInDesignMode() + { + // Mirrors a user explicitly enabling the built-in console logger in run settings while in design + // mode. Design mode only suppresses adding the *default* logger at activation time; a logger the + // user asked for is still activated. The friendly name resolves to the logger URI, which the + // composition root's factory then services with an injected instance instead of a + // reflection-activated one. + var injectedLogger = new Mock(); + var requestData = new RequestData + { + KnownExtensionInstanceFactory = uri => + uri.AbsoluteUri == new Uri(_loggerUri).AbsoluteUri ? injectedLogger.Object : null, + }; + var testLoggerManager = new DummyTestLoggerManager(requestData); + + string settingsXml = + @" + + + true + + + + + + + "; + + testLoggerManager.Initialize(settingsXml); + + injectedLogger.Verify(l => l.Initialize(It.IsAny(), It.IsAny()), Times.Once); + } + + [TestMethod] + public void InitializeLoggerByUriShouldFallBackToReflectionWhenTheCompositionRootHasNoInstance() + { + // Loggers the composition root does not know about (third-party loggers loaded via + // /testadapterpath, and any of our own not wired into the factory) must continue to be + // reflection-activated exactly as before: the factory returns null and we fall through to the + // extension manager. This keeps the injection seam closed to our own built-in extensions and + // does not widen any public extension point. + var requestData = new RequestData(); + var testLoggerManager = new DummyTestLoggerManager(requestData); + + var initialized = testLoggerManager.InitializeLoggerByUri(new Uri(_loggerUri), new()); + + Assert.IsTrue(initialized); + } + + [TestMethod] + public void InitializeByAssemblyQualifiedNameShouldUseTheInstanceSuppliedByTheCompositionRootForAKnownLogger() + { + // This is the path our shipped ConsoleLogger actually takes: it is registered in run settings by + // assembly-qualified name (see TestRequestManager.AddConsoleLogger), not discovered through the + // logger extension manager, so it is activated by InitializeLoggerByType rather than by URI. The + // composition root's factory must service that path too, keyed by the extension URI declared on + // the resolved type, so the known logger receives its injected dependencies instead of being + // reflection-activated. Removing ConsoleLogger's parameterless constructor depends on exactly this. + var injectedLogger = new Mock(); + var requestData = new RequestData + { + KnownExtensionInstanceFactory = uri => + uri.AbsoluteUri == new Uri(_loggerUri).AbsoluteUri ? injectedLogger.Object : null, + }; + var testLoggerManager = new DummyTestLoggerManager(requestData); + + var assemblyQualifiedName = typeof(ValidLogger).AssemblyQualifiedName; + var codeBase = typeof(TestLoggerManagerTests).Assembly.Location; + + string settingsXml = + @" + + + + + + + "; + + testLoggerManager.Initialize(settingsXml); + + injectedLogger.Verify(l => l.Initialize(It.IsAny(), It.IsAny()), Times.Once); + } + [TestMethod] public void AddLoggerShouldAddDefaultLoggerParameterForTestLoggerWithParameters() { From 4b0c2cd5ab2cd002aa60c3a19720f2edb6f9575b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Thu, 9 Jul 2026 15:27:53 +0200 Subject: [PATCH 49/87] Revert "Source bundled sibling DLLs from producer output in TranslationLayer package" (#16253) This reverts the packaging change from #16251. The "source each payload from its producer's own output" pattern earns its keep only when many projects with different target frameworks publish into one shared folder, so a single filename can be captured in the wrong framework flavor (the .dll / .config binding-redirect split fixed for the VS bundle, CLI, and Portable packages). Microsoft.TestPlatform.TranslationLayer is an ordinary multi-targeting library: its $(OutputPath) is per-TFM, so the sibling Common/CommunicationUtilities DLL in each folder is already the single correct flavor - there is no commingling to protect against. Worse, because Common has no net8.0 build, the conversion had to hand-roll a net8.0 -> netstandard2.0 fallback that the build previously resolved for free when the ProjectReference was copied into $(OutputPath). That extra logic added complexity for zero benefit. Reverting restores the shorter, clearer $(OutputPath) form. The conversion was a strict no-op (+0/-0, no DLL reclassified), so this revert is equally a no-op: same files, same flavors, less indirection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...form.VsTestConsole.TranslationLayer.csproj | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.csproj b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.csproj index d8876719c5..29624b0f82 100644 --- a/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.csproj +++ b/src/Microsoft.TestPlatform.VsTestConsole.TranslationLayer/Microsoft.TestPlatform.VsTestConsole.TranslationLayer.csproj @@ -43,31 +43,17 @@ - + - - - <_TranslationLayerCommonTfm>$(TargetFramework) - <_TranslationLayerCommonTfm Condition="!Exists('$(ArtifactsBinDir)Microsoft.TestPlatform.Common\$(Configuration)\$(TargetFramework)\')">netstandard2.0 - - + - - + + - - - + + + From b93e72a6cd4809a6ae69ed9fba89cab5d5ef7176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Thu, 9 Jul 2026 15:32:16 +0200 Subject: [PATCH 50/87] Ship real testhost.deps.json, keep only the latest native fallback runtimeconfig (#16237) The built-in testhost fallback (native C++ runners that bring no managed testhost) shipped a hand-crafted testhost.deps.json in temp/testhost/ and 14 version-specific testhost-.runtimeconfig.json files. The artificial deps.json was already dead - no build file referenced it, and the packages already ship the real generated testhost.deps.json. Native runners always roll forward to testhost-latest, so the version-specific configs were dead too. Drop the artificial deps.json and the 13 version configs, keep testhost-latest, and simplify the DotnetTestHostManager fallback to always use it - only native dlls get here, managed ones are already rejected with Microsoft.NET.Test.Sdk guidance. Verified with the native C++ fallback acceptance test. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/instructions/testhost.instructions.md | 2 +- eng/expected-nupkg-file-counts.json | 4 +- .../Hosting/DotnetTestHostManager.cs | 54 ++----- .../Microsoft.TestPlatform.CLI.csproj | 29 +--- .../Microsoft.TestPlatform.csproj | 13 -- temp/testhost/testhost-1.0.runtimeconfig.json | 9 -- temp/testhost/testhost-1.1.runtimeconfig.json | 9 -- .../testhost/testhost-10.0.runtimeconfig.json | 9 -- .../testhost/testhost-11.0.runtimeconfig.json | 9 -- temp/testhost/testhost-2.0.runtimeconfig.json | 9 -- temp/testhost/testhost-2.1.runtimeconfig.json | 9 -- temp/testhost/testhost-3.0.runtimeconfig.json | 9 -- temp/testhost/testhost-3.1.runtimeconfig.json | 9 -- temp/testhost/testhost-5.0.runtimeconfig.json | 9 -- temp/testhost/testhost-6.0.runtimeconfig.json | 9 -- temp/testhost/testhost-7.0.runtimeconfig.json | 9 -- temp/testhost/testhost-8.0.runtimeconfig.json | 9 -- temp/testhost/testhost-9.0.runtimeconfig.json | 9 -- temp/testhost/testhost.deps.json | 146 ------------------ .../Hosting/DotnetTestHostManagerTests.cs | 22 +-- 20 files changed, 26 insertions(+), 361 deletions(-) delete mode 100644 temp/testhost/testhost-1.0.runtimeconfig.json delete mode 100644 temp/testhost/testhost-1.1.runtimeconfig.json delete mode 100644 temp/testhost/testhost-10.0.runtimeconfig.json delete mode 100644 temp/testhost/testhost-11.0.runtimeconfig.json delete mode 100644 temp/testhost/testhost-2.0.runtimeconfig.json delete mode 100644 temp/testhost/testhost-2.1.runtimeconfig.json delete mode 100644 temp/testhost/testhost-3.0.runtimeconfig.json delete mode 100644 temp/testhost/testhost-3.1.runtimeconfig.json delete mode 100644 temp/testhost/testhost-5.0.runtimeconfig.json delete mode 100644 temp/testhost/testhost-6.0.runtimeconfig.json delete mode 100644 temp/testhost/testhost-7.0.runtimeconfig.json delete mode 100644 temp/testhost/testhost-8.0.runtimeconfig.json delete mode 100644 temp/testhost/testhost-9.0.runtimeconfig.json delete mode 100644 temp/testhost/testhost.deps.json diff --git a/.github/instructions/testhost.instructions.md b/.github/instructions/testhost.instructions.md index 6bf6157801..3b9c547e76 100644 --- a/.github/instructions/testhost.instructions.md +++ b/.github/instructions/testhost.instructions.md @@ -8,7 +8,7 @@ Testhost processes execute user tests. Assembly loading correctness and framewor ## Assembly Loading & Resolution -- Keep `testhost.deps.json` dependency versions aligned with assemblies actually shipped in the CLI package. +- The built-in testhost fallback for native (C++) runners ships the real generated `testhost.deps.json` (built from `testhost.dll`) next to the runner, so its dependency versions match the shipped assemblies by construction — it is not hand-maintained. - Prefer bin-directory resolution over deps.json parsing for speed and correctness. - TypeLoadException from version mismatches must produce diagnostic output identifying the missing assembly. - Test deps.json edge cases: self-contained apps, single-file publish, RID-specific native assets. diff --git a/eng/expected-nupkg-file-counts.json b/eng/expected-nupkg-file-counts.json index 5b2a411bcb..c1273d9daf 100644 --- a/eng/expected-nupkg-file-counts.json +++ b/eng/expected-nupkg-file-counts.json @@ -1,10 +1,10 @@ { "Microsoft.CodeCoverage": 81, "Microsoft.NET.Test.Sdk": 26, - "Microsoft.TestPlatform": 553, + "Microsoft.TestPlatform": 540, "Microsoft.TestPlatform.AdapterUtilities": 66, "Microsoft.TestPlatform.Build": 22, - "Microsoft.TestPlatform.CLI": 483, + "Microsoft.TestPlatform.CLI": 470, "Microsoft.TestPlatform.Extensions.TrxLogger": 37, "Microsoft.TestPlatform.Filter.Source": 13, "Microsoft.TestPlatform.Internal.Uwp": 39, diff --git a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs index 8cc3471d56..26575675b7 100644 --- a/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs +++ b/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs @@ -443,12 +443,11 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo( EqtTrace.Verbose("DotnetTestHostmanager: Adding {0} in args", argsToAdd); // Additional deps will contain relative paths, tell the process to search for the dlls also - // next to the testhost.dll. The additional deps file is specially crafted to keep all the - // .dlls in the root folder, by only referencing libraries, and setting the path to "/". - // Without this, e.g. using the normal deps.json that is generated when testhost.dll is built, - // dotnet would consider additional deps path as the root of a Nuget package source, - // and would try to locate the dlls in a more complicated folder structure, and would fail to - // find those dependencies. + // next to the testhost.dll. testhost.deps.json is the real deps.json produced when testhost.dll + // is built, shipped next to testhost.dll. Its dependency paths follow a NuGet package layout, + // so on its own dotnet would look for the dlls in a nested folder structure and fail to find them. + // We ship all the testhost dependencies flat next to testhost.dll and add that folder as an + // additional probing path below, which lets dotnet resolve every dependency from there. // // If they were in the base path (where the test dll is) it would work // fine, because in base folder, dotnet searches directly in that folder, but not in probing paths. @@ -459,42 +458,15 @@ public virtual TestProcessStartInfo GetTestHostProcessStartInfo( if (!runtimeConfigFound) { - // When runtime config is not found, we don't know which version exactly should be selected for the runtime. - // This can happen when the test project is .NET (Core) but does not have EXE output type, or when the dll is native. + // Only native (e.g. C++) sources reach this point. A managed source that did not resolve its + // own runtime config threw above and pointed the user at Microsoft.NET.Test.Sdk. A native dll + // carries no target framework information, so we don't know - and don't care - which runtime + // version it runs on. We point it at testhost-latest.runtimeconfig.json, which rolls forward to + // the latest installed runtime, giving us the best chance of finding a runtime to launch on. // - // When the project is .NET (Core) we can look at the TargetFramework and gather the rough version from there. We then - // provide a runtime config targetting that version. It rolls forward on the minor version by default, so the latest - // version that is present will be selected in that range. Same as if you had EXE and no special settings. - // E.g. the dll targets netcoreapp3.1, we get 3.1 from the attribute in the Dll, and provide testhost-3.1.runtimeconfig.json - // this will resolve to 3.1.17 runtime because that is the latest installed on the system. - // - // - // In the other case, where the Dll is native, we take the a runtime config that will roll forward to the latest version - // because we don't care on which version we will run, and rolling forward gives us the best chance of findind some runtime. - // - // - // There are 2 options how to provide the runtime version. Using --runtimeconfig, and --fx-version. The --fx-version does - // not roll forward even when the --roll-forward option is provided (or --roll-forward-on-no-candidate-fx for netcoreapp2.1) - // and we don't know the exact version we want to use. So the only option for us is to use the runtimeconfig.json. - // - // - // TODO: This version check is a hack, when the target framework is figured out it tries to unify to a single common framework - // even if there are incompatible frameworks (e.g any .NET Framwork assembly and any .NET (Core) assembly). Those incompatibilities - // will fall back to a common default framework. And that framework (stored in Framework.DefaultFramework) depends on compile time variables - // so depending on the version of vstest.console you are using, you will get a different value. This value for vstest.console.exe (under VS) - // is .NET Framework 4, but for vstest.console.dll (under dotnet test) is .NET Core 1.0. Those values are also valid values, so we have no idea - // if user actually provided a .NET Core 1.0 dll, or we are using fallback because we are running under vstest.console, and there is conflict, - // or if user provided native dll which does not have the attribute (that we read via PEReader). - // - // Another aspect of this is that we are unifying the dlls, so until we add per assembly data, this would be less accurate than using runtimeconfig.json - // but we can work around that by 1) changing how we schedule runners, to make sure we can process more that 1 type of assembly in vstest.console and - // 2) making sure we still make the project executable (and so we actually do get runtimeconfig unless the user tries hard to not make the test and EXE). - var suffix = _targetFramework.Version == "1.0.0.0" ? "latest" : $"{new Version(_targetFramework.Version).Major}.{new Version(_targetFramework.Version).Minor}"; - var testhostRuntimeConfig = Path.Combine(Path.GetDirectoryName(testHostNextToRunner)!, $"testhost-{suffix}.runtimeconfig.json"); - if (!_fileHelper.Exists(testhostRuntimeConfig)) - { - testhostRuntimeConfig = Path.Combine(Path.GetDirectoryName(testHostNextToRunner)!, $"testhost-latest.runtimeconfig.json"); - } + // We use --runtimeconfig rather than --fx-version because --fx-version pins an exact version and + // does not roll forward (even with --roll-forward), and we don't know which version is installed. + var testhostRuntimeConfig = Path.Combine(Path.GetDirectoryName(testHostNextToRunner)!, "testhost-latest.runtimeconfig.json"); argsToAdd = " --runtimeconfig " + testhostRuntimeConfig.AddDoubleQuote(); args += argsToAdd; diff --git a/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.csproj b/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.csproj index 6fbcc83013..c05df4b274 100644 --- a/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.csproj +++ b/src/package/Microsoft.TestPlatform.CLI/Microsoft.TestPlatform.CLI.csproj @@ -50,20 +50,7 @@ - - - - - - - - - - - - - - + @@ -204,19 +191,7 @@ - - - - - - - - - - - - - + diff --git a/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.csproj b/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.csproj index 03ae13c436..fe27214a0d 100644 --- a/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.csproj +++ b/src/package/Microsoft.TestPlatform/Microsoft.TestPlatform.csproj @@ -276,19 +276,6 @@ - - - - - - - - - - - - - diff --git a/temp/testhost/testhost-1.0.runtimeconfig.json b/temp/testhost/testhost-1.0.runtimeconfig.json deleted file mode 100644 index daff84e22d..0000000000 --- a/temp/testhost/testhost-1.0.runtimeconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "netcoreapp1.0", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "1.0.0-preview.0" - } - } -} \ No newline at end of file diff --git a/temp/testhost/testhost-1.1.runtimeconfig.json b/temp/testhost/testhost-1.1.runtimeconfig.json deleted file mode 100644 index 41de64091a..0000000000 --- a/temp/testhost/testhost-1.1.runtimeconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "netcoreapp1.1", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "1.1.0-preview.0" - } - } -} \ No newline at end of file diff --git a/temp/testhost/testhost-10.0.runtimeconfig.json b/temp/testhost/testhost-10.0.runtimeconfig.json deleted file mode 100644 index e1d32eb6cd..0000000000 --- a/temp/testhost/testhost-10.0.runtimeconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "net10.0", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "10.0.0-preview.0" - } - } -} diff --git a/temp/testhost/testhost-11.0.runtimeconfig.json b/temp/testhost/testhost-11.0.runtimeconfig.json deleted file mode 100644 index 7c01deab38..0000000000 --- a/temp/testhost/testhost-11.0.runtimeconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "net11.0", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "11.0.0-preview.0" - } - } -} diff --git a/temp/testhost/testhost-2.0.runtimeconfig.json b/temp/testhost/testhost-2.0.runtimeconfig.json deleted file mode 100644 index 646bd0e44f..0000000000 --- a/temp/testhost/testhost-2.0.runtimeconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "netcoreapp2.0", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "2.0.0-preview.0" - } - } -} \ No newline at end of file diff --git a/temp/testhost/testhost-2.1.runtimeconfig.json b/temp/testhost/testhost-2.1.runtimeconfig.json deleted file mode 100644 index c45472e093..0000000000 --- a/temp/testhost/testhost-2.1.runtimeconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "netcoreapp2.1", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "2.1.0-preview.0" - } - } -} \ No newline at end of file diff --git a/temp/testhost/testhost-3.0.runtimeconfig.json b/temp/testhost/testhost-3.0.runtimeconfig.json deleted file mode 100644 index 7a6cdcd74e..0000000000 --- a/temp/testhost/testhost-3.0.runtimeconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "netcoreapp3.0", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "3.0.0-preview.0" - } - } -} \ No newline at end of file diff --git a/temp/testhost/testhost-3.1.runtimeconfig.json b/temp/testhost/testhost-3.1.runtimeconfig.json deleted file mode 100644 index a56c023e77..0000000000 --- a/temp/testhost/testhost-3.1.runtimeconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "netcoreapp3.1", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "3.1.0-preview.0" - } - } -} \ No newline at end of file diff --git a/temp/testhost/testhost-5.0.runtimeconfig.json b/temp/testhost/testhost-5.0.runtimeconfig.json deleted file mode 100644 index 5aef73d4d2..0000000000 --- a/temp/testhost/testhost-5.0.runtimeconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "net5.0", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "5.0.0-preview.0" - } - } -} \ No newline at end of file diff --git a/temp/testhost/testhost-6.0.runtimeconfig.json b/temp/testhost/testhost-6.0.runtimeconfig.json deleted file mode 100644 index ee103ec980..0000000000 --- a/temp/testhost/testhost-6.0.runtimeconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "net6.0", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "6.0.0-preview.0" - } - } -} \ No newline at end of file diff --git a/temp/testhost/testhost-7.0.runtimeconfig.json b/temp/testhost/testhost-7.0.runtimeconfig.json deleted file mode 100644 index 23ecc9f38b..0000000000 --- a/temp/testhost/testhost-7.0.runtimeconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "net7.0", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "7.0.0-preview.0" - } - } -} \ No newline at end of file diff --git a/temp/testhost/testhost-8.0.runtimeconfig.json b/temp/testhost/testhost-8.0.runtimeconfig.json deleted file mode 100644 index 15af9f03a0..0000000000 --- a/temp/testhost/testhost-8.0.runtimeconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "net8.0", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "8.0.0-preview.0" - } - } -} diff --git a/temp/testhost/testhost-9.0.runtimeconfig.json b/temp/testhost/testhost-9.0.runtimeconfig.json deleted file mode 100644 index 146e13202e..0000000000 --- a/temp/testhost/testhost-9.0.runtimeconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "net9.0", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "9.0.0-preview.0" - } - } -} diff --git a/temp/testhost/testhost.deps.json b/temp/testhost/testhost.deps.json deleted file mode 100644 index 8d751253a8..0000000000 --- a/temp/testhost/testhost.deps.json +++ /dev/null @@ -1,146 +0,0 @@ -{ - "runtimeTarget": { - "name": ".NETCoreApp,Version=v2.1", - "signature": "" - }, - "compilationOptions": {}, - "targets": { - ".NETCoreApp,Version=v2.1": { - "testhost/1.0.0": { - "dependencies": { - "Microsoft.TestPlatform.CommunicationUtilities": "15.0.0.0", - "Microsoft.TestPlatform.CoreUtilities": "15.0.0.0", - "Microsoft.TestPlatform.CrossPlatEngine": "15.0.0.0", - "Microsoft.TestPlatform.PlatformAbstractions": "15.0.0.0", - "Microsoft.TestPlatform.Utilities": "15.0.0.0", - "Microsoft.VisualStudio.TestPlatform.Common": "15.0.0.0", - "Microsoft.VisualStudio.TestPlatform.ObjectModel": "15.0.0.0", - "Newtonsoft.Json": "13.0.0.0" - }, - "runtime": { - "testhost.dll": {} - } - }, - "Microsoft.TestPlatform.CommunicationUtilities/15.0.0.0": { - "runtime": { - "Microsoft.TestPlatform.CommunicationUtilities.dll": { - "assemblyVersion": "15.0.0.0", - "fileVersion": "15.0.0.0" - } - } - }, - "Microsoft.TestPlatform.CoreUtilities/15.0.0.0": { - "runtime": { - "Microsoft.TestPlatform.CoreUtilities.dll": { - "assemblyVersion": "15.0.0.0", - "fileVersion": "15.0.0.0" - } - } - }, - "Microsoft.TestPlatform.CrossPlatEngine/15.0.0.0": { - "runtime": { - "Microsoft.TestPlatform.CrossPlatEngine.dll": { - "assemblyVersion": "15.0.0.0", - "fileVersion": "15.0.0.0" - } - } - }, - "Microsoft.TestPlatform.PlatformAbstractions/15.0.0.0": { - "runtime": { - "Microsoft.TestPlatform.PlatformAbstractions.dll": { - "assemblyVersion": "15.0.0.0", - "fileVersion": "15.0.0.0" - } - } - }, - "Microsoft.TestPlatform.Utilities/15.0.0.0": { - "runtime": { - "Microsoft.TestPlatform.Utilities.dll": { - "assemblyVersion": "15.0.0.0", - "fileVersion": "15.0.0.0" - } - } - }, - "Microsoft.VisualStudio.TestPlatform.Common/15.0.0.0": { - "runtime": { - "Microsoft.VisualStudio.TestPlatform.Common.dll": { - "assemblyVersion": "15.0.0.0", - "fileVersion": "15.0.0.0" - } - } - }, - "Microsoft.VisualStudio.TestPlatform.ObjectModel/15.0.0.0": { - "runtime": { - "Microsoft.VisualStudio.TestPlatform.ObjectModel.dll": { - "assemblyVersion": "15.0.0.0", - "fileVersion": "15.0.0.0" - } - } - }, - "Newtonsoft.Json/13.0.0.0": { - "runtime": { - "Newtonsoft.Json.dll": { - "assemblyVersion": "13.0.0.0", - "fileVersion": "13.0.3.25517" - } - } - } - } - }, - "libraries": { - "testhost/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "", - "path": "/" - }, - "Microsoft.TestPlatform.CommunicationUtilities/15.0.0.0": { - "type": "reference", - "serviceable": false, - "sha512": "", - "path": "/" - }, - "Microsoft.TestPlatform.CoreUtilities/15.0.0.0": { - "type": "reference", - "serviceable": false, - "sha512": "", - "path": "/" - }, - "Microsoft.TestPlatform.CrossPlatEngine/15.0.0.0": { - "type": "reference", - "serviceable": false, - "sha512": "", - "path": "/" - }, - "Microsoft.TestPlatform.PlatformAbstractions/15.0.0.0": { - "type": "reference", - "serviceable": false, - "sha512": "", - "path": "/" - }, - "Microsoft.TestPlatform.Utilities/15.0.0.0": { - "type": "reference", - "serviceable": false, - "sha512": "", - "path": "/" - }, - "Microsoft.VisualStudio.TestPlatform.Common/15.0.0.0": { - "type": "reference", - "serviceable": false, - "sha512": "", - "path": "/" - }, - "Microsoft.VisualStudio.TestPlatform.ObjectModel/15.0.0.0": { - "type": "reference", - "serviceable": false, - "sha512": "", - "path": "/" - }, - "Newtonsoft.Json/13.0.0.0": { - "type": "reference", - "serviceable": false, - "sha512": "", - "path": "/" - } - } -} \ No newline at end of file diff --git a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs index af64cf5c36..ef92229e43 100644 --- a/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs +++ b/test/Microsoft.TestPlatform.TestHostProvider.UnitTests/Hosting/DotnetTestHostManagerTests.cs @@ -730,17 +730,13 @@ public void GetTestHostProcessStartInfoShouldIncludeTestHostPathNextToTestRunner [TestMethod] - // we can't put in a "default" value, and we don't have other way to determine if this provided value is the - // runtime default or the actual value that user provided, so right now the default will use the latest, instead - // or the more correct 1.0, it should be okay, as that version is not supported anymore anyway - [DataRow("net8.0", "8.0", true)] - - // net9.0 is currently the latest released version, but it still has it's own runtime config, it is not the same as - // "latest" which means the latest you have on system. So if you have only 5.0 SDK then net8.0 will fail because it can't find net8.0, - // but latest would use net9.0 because that is the latest one on your system. - [DataRow("net9.0", "9.0", true)] - [DataRow("net9.0", "latest", false)] - public void GetTestHostProcessStartInfoShouldIncludeTestHostPathNextToTestRunnerIfTesthostDllIsNoFoundAndDepsFileNotFoundWithTheCorrectTfm(string tfm, string suffix, bool runtimeConfigExists) + // A native (e.g. C++) source has no real target framework. Even when a TargetFrameworkVersion ends up in + // the run settings (e.g. because vstest.console unified incompatible assemblies to a default framework, or + // the user set one), the built-in testhost fallback always rolls forward to the latest installed runtime via + // testhost-latest.runtimeconfig.json. We no longer ship version-specific testhost-.runtimeconfig.json files. + [DataRow("net8.0")] + [DataRow("net9.0")] + public void GetTestHostProcessStartInfoNativeFallbackAlwaysUsesLatestRuntimeConfigRegardlessOfTfm(string tfm) { // Absolute path to the source directory var sourcePath = Path.Combine(_temp, "test.dll"); @@ -755,12 +751,10 @@ public void GetTestHostProcessStartInfoShouldIncludeTestHostPathNextToTestRunner var testhostNextToRunner = Path.Combine(here, "testhost.dll"); _mockFileHelper.Setup(ph => ph.Exists(testhostNextToRunner)).Returns(true); - _mockFileHelper.Setup(ph => ph.Exists(It.Is(s => s.Contains($"{suffix}.runtimeconfig.json")))).Returns(runtimeConfigExists); - _dotnetHostManager.Initialize(_mockMessageLogger.Object, $"{tfm}"); var startInfo = _dotnetHostManager.GetTestHostProcessStartInfo(new[] { sourcePath }, null, _defaultConnectionInfo); - var expectedRuntimeConfigPath = Path.Combine(here, $"testhost-{suffix}.runtimeconfig.json"); + var expectedRuntimeConfigPath = Path.Combine(here, "testhost-latest.runtimeconfig.json"); Assert.Contains($"--runtimeconfig \"{expectedRuntimeConfigPath}\"", startInfo.Arguments!); } From 7bc64d2f093c0877c674278394b72d902bf7cf74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Thu, 9 Jul 2026 15:51:06 +0200 Subject: [PATCH 51/87] Stop producing Microsoft.TestPlatform.Internal.Uwp (#16249) * Stop producing Microsoft.TestPlatform.Internal.Uwp This is an internal (non-shipping) packaging container. It builds no assembly of its own, it only re-bundles ObjectModel, CrossPlatEngine and their dependencies as netstandard2.0 for the UWP runner. Every DLL it ships already ships in other packages, the only thing unique here is the full engine set exposed as netstandard2.0 references. TestHost ships the same set but only as net8.0. I am not sure this is still needed, maybe the netstandard build was too old at some point and UWP needed newer dlls. Removing it to see if any consumer actually depends on it. It is internal, so a revert brings it back. - Remove the project and its solution entry. - Drop its entries from the two package-verification manifests. Verified: build.cmd -c Release -pack passes locally, verify-nupkgs is green and the package is no longer produced. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Only publish NonShipping packages when the folder exists Removing the Internal.Uwp package left no non-shipping packages, so the NonShipping folder is no longer produced and 'Publish NonShipping Packages' failed on a missing path. Gate the publish on the folder actually containing packages. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- TestPlatform.slnx | 1 - azure-pipelines.yml | 10 ++++ eng/expected-dll-frameworks.json | 9 ---- eng/expected-nupkg-file-counts.json | 1 - ...Microsoft.TestPlatform.Internal.Uwp.csproj | 49 ------------------- .../README.md | 16 ------ 6 files changed, 10 insertions(+), 76 deletions(-) delete mode 100644 src/package/Microsoft.TestPlatform.Internal.Uwp/Microsoft.TestPlatform.Internal.Uwp.csproj delete mode 100644 src/package/Microsoft.TestPlatform.Internal.Uwp/README.md diff --git a/TestPlatform.slnx b/TestPlatform.slnx index 11d17f9705..6f8db2d7c1 100644 --- a/TestPlatform.slnx +++ b/TestPlatform.slnx @@ -63,7 +63,6 @@ - diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3a08eb3621..71486c81cb 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -133,8 +133,18 @@ stages: PathtoPublish: '$(Build.SourcesDirectory)/artifacts/packages/$(_BuildConfig)/Shipping' ArtifactName: PackageArtifacts + # NonShipping packages are optional. When there are none, the folder is not + # produced and PublishBuildArtifacts would fail on a missing path, so only + # publish when the folder actually contains packages. + - powershell: | + $dir = "$(Build.SourcesDirectory)/artifacts/packages/$(_BuildConfig)/NonShipping" + $hasFiles = (Test-Path $dir) -and (@(Get-ChildItem -Path $dir -File -Recurse -ErrorAction SilentlyContinue).Count -gt 0) + Write-Host "##vso[task.setvariable variable=HasNonShippingPackages]$($hasFiles.ToString().ToLowerInvariant())" + displayName: 'Check for NonShipping Packages' + - task: PublishBuildArtifacts@1 displayName: 'Publish NonShipping Packages' + condition: and(succeeded(), eq(variables['HasNonShippingPackages'], 'true')) inputs: PathtoPublish: '$(Build.SourcesDirectory)/artifacts/packages/$(_BuildConfig)/NonShipping' ArtifactName: PackageArtifacts diff --git a/eng/expected-dll-frameworks.json b/eng/expected-dll-frameworks.json index 0e3faeb53a..e3eabcee25 100644 --- a/eng/expected-dll-frameworks.json +++ b/eng/expected-dll-frameworks.json @@ -305,15 +305,6 @@ "lib/net462/Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger.dll": "netframework", "lib/netstandard2.0/Microsoft.VisualStudio.TestPlatform.Extensions.Trx.TestLogger.dll": "netstandard" }, - "Microsoft.TestPlatform.Internal.Uwp": { - "lib/netstandard2.0/Microsoft.TestPlatform.CommunicationUtilities.dll": "netstandard", - "lib/netstandard2.0/Microsoft.TestPlatform.CoreUtilities.dll": "netstandard", - "lib/netstandard2.0/Microsoft.TestPlatform.CrossPlatEngine.dll": "netstandard", - "lib/netstandard2.0/Microsoft.TestPlatform.PlatformAbstractions.dll": "netstandard", - "lib/netstandard2.0/Microsoft.TestPlatform.Utilities.dll": "netstandard", - "lib/netstandard2.0/Microsoft.VisualStudio.TestPlatform.Common.dll": "netstandard", - "lib/netstandard2.0/Microsoft.VisualStudio.TestPlatform.ObjectModel.dll": "netstandard" - }, "Microsoft.TestPlatform.ObjectModel": { "lib/net462/Microsoft.TestPlatform.CoreUtilities.dll": "netframework", "lib/net462/Microsoft.TestPlatform.PlatformAbstractions.dll": "netframework", diff --git a/eng/expected-nupkg-file-counts.json b/eng/expected-nupkg-file-counts.json index c1273d9daf..3c40a6cd71 100644 --- a/eng/expected-nupkg-file-counts.json +++ b/eng/expected-nupkg-file-counts.json @@ -7,7 +7,6 @@ "Microsoft.TestPlatform.CLI": 470, "Microsoft.TestPlatform.Extensions.TrxLogger": 37, "Microsoft.TestPlatform.Filter.Source": 13, - "Microsoft.TestPlatform.Internal.Uwp": 39, "Microsoft.TestPlatform.ObjectModel": 96, "Microsoft.TestPlatform.Portable": 608, "Microsoft.TestPlatform.TestHost": 65, diff --git a/src/package/Microsoft.TestPlatform.Internal.Uwp/Microsoft.TestPlatform.Internal.Uwp.csproj b/src/package/Microsoft.TestPlatform.Internal.Uwp/Microsoft.TestPlatform.Internal.Uwp.csproj deleted file mode 100644 index e2ca981baf..0000000000 --- a/src/package/Microsoft.TestPlatform.Internal.Uwp/Microsoft.TestPlatform.Internal.Uwp.csproj +++ /dev/null @@ -1,49 +0,0 @@ - - - netstandard2.0 - - - - true - - false - - false - true - - $(NoWarn);NU5128 - Microsoft.TestPlatform.Internal.Uwp - vstest visual-studio unittest testplatform mstest microsoft test testing - - Internal Microsoft Test Platform libraries for UWP runner. - - README.md - $(TargetsForTfmSpecificContentInPackage);IncludeBundledAssembliesInPackage - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/package/Microsoft.TestPlatform.Internal.Uwp/README.md b/src/package/Microsoft.TestPlatform.Internal.Uwp/README.md deleted file mode 100644 index 93e32842a1..0000000000 --- a/src/package/Microsoft.TestPlatform.Internal.Uwp/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Microsoft.TestPlatform.Internal.Uwp - -Internal test platform libraries for the UWP (Universal Windows Platform) test runner. This package provides the test platform binaries needed to run tests in UWP applications. - -> **Note:** This is an internal package. Most test projects should reference `Microsoft.NET.Test.Sdk` instead. - -## Usage - -```xml - -``` - -## Links - -- [Visual Studio Test Platform Documentation](https://github.com/microsoft/vstest) -- [License (MIT)](https://github.com/microsoft/vstest/blob/main/LICENSE) From 455925fb52ee441d0ad8621b737ad7677d6a4b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Thu, 9 Jul 2026 17:09:13 +0200 Subject: [PATCH 52/87] Make the built-in ConsoleLogger independent of CommandLineOptions.Instance (#16255) After #16248 the console logger is activated from the composition root with an injected CommandLineOptions, so the ?? CommandLineOptions.Instance fallback was already dead in production. Remove it, make the field non-nullable, and default the test constructor to a fresh CommandLineOptions. The logger no longer reads the process-wide singleton, and its tests use a per-instance options object instead of resetting and poking the static one. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/vstest.console/Internal/ConsoleLogger.cs | 10 +++--- .../Internal/ConsoleLoggerTests.cs | 35 +++++++++---------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/vstest.console/Internal/ConsoleLogger.cs b/src/vstest.console/Internal/ConsoleLogger.cs index 5c5096f135..e76a3931c5 100644 --- a/src/vstest.console/Internal/ConsoleLogger.cs +++ b/src/vstest.console/Internal/ConsoleLogger.cs @@ -131,7 +131,9 @@ internal ConsoleLogger(IOutput output, IProgressIndicator progressIndicator, IFe Output = output; _progressIndicator = progressIndicator; _featureFlag = featureFlag; - _commandLineOptions = commandLineOptions; + // Never fall back to CommandLineOptions.Instance: the logger owns its own options so tests + // stay isolated and the built-in logger no longer reads the process-wide singleton. + _commandLineOptions = commandLineOptions ?? new CommandLineOptions(); } /// @@ -148,7 +150,7 @@ protected static IOutput? Output private readonly IFeatureFlag _featureFlag = FeatureFlag.Instance; - private readonly CommandLineOptions? _commandLineOptions; + private readonly CommandLineOptions _commandLineOptions; /// /// Get the verbosity level for the console logger @@ -423,7 +425,7 @@ private void TestRunStartHandler(object? sender, TestRunStartEventArgs e) TPDebug.Assert(Output != null, "Initialize should have been called"); // Print all test containers. - var commandLineOptions = _commandLineOptions ?? CommandLineOptions.Instance; + var commandLineOptions = _commandLineOptions; Output.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestSourcesDiscovered, commandLineOptions.Sources.Count()), OutputLevel.Information); if (VerbosityLevel == Verbosity.Detailed) { @@ -693,7 +695,7 @@ private void TestRunCompleteHandler(object? sender, TestRunCompleteEventArgs e) // DISABLE_ARTIFACTS_POSTPROCESSING_NEW_SDK_UX(new UX) is disabled _featureFlag.IsSet(FeatureFlag.VSTEST_DISABLE_ARTIFACTS_POSTPROCESSING_NEW_SDK_UX) || // TestSessionCorrelationId is null(we're not running through the dotnet SDK). - (_commandLineOptions ?? CommandLineOptions.Instance).TestSessionCorrelationId is null) + _commandLineOptions.TestSessionCorrelationId is null) { Output.Information(false, CommandLineResources.AttachmentsBanner); TPDebug.Assert(e.AttachmentSets != null, "e.AttachmentSets should not be null when runLevelAttachmentsCount > 0."); diff --git a/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs b/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs index 2ba642c910..32a8d0535e 100644 --- a/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs +++ b/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs @@ -38,6 +38,7 @@ public class ConsoleLoggerTests private readonly ConsoleLogger _consoleLogger; private readonly Mock _mockProgressIndicator; private readonly Mock _mockFeatureFlag; + private readonly CommandLineOptions _commandLineOptions; private const string PassedTestIndicator = " Passed "; private const string FailedTestIndicator = " Failed "; @@ -53,7 +54,8 @@ public ConsoleLoggerTests() _mockOutput = new Mock(); _mockProgressIndicator = new Mock(); - _consoleLogger = new ConsoleLogger(_mockOutput.Object, _mockProgressIndicator.Object, _mockFeatureFlag.Object); + _commandLineOptions = new CommandLineOptions(); + _consoleLogger = new ConsoleLogger(_mockOutput.Object, _mockProgressIndicator.Object, _mockFeatureFlag.Object, _commandLineOptions); RunTestsArgumentProcessorTests.SetupMockExtensions(); } @@ -1046,13 +1048,12 @@ public void TestRunStartHandlerShouldWriteNumberOfTestSourcesDiscoveredOnConsole loggerEvents.EnableEvents(); var fileHelper = new Mock(); - CommandLineOptions.Reset(); - CommandLineOptions.Instance.FileHelper = fileHelper.Object; - CommandLineOptions.Instance.FilePatternParser = new FilePatternParser(new Mock().Object, fileHelper.Object); + _commandLineOptions.FileHelper = fileHelper.Object; + _commandLineOptions.FilePatternParser = new FilePatternParser(new Mock().Object, fileHelper.Object); string testFilePath = Path.Combine(Path.GetTempPath(), "DmmyTestFile.dll"); fileHelper.Setup(fh => fh.Exists(testFilePath)).Returns(true); - CommandLineOptions.Instance.AddSource(testFilePath); + _commandLineOptions.AddSource(testFilePath); var parameters = new Dictionary { @@ -1064,7 +1065,7 @@ public void TestRunStartHandlerShouldWriteNumberOfTestSourcesDiscoveredOnConsole loggerEvents.RaiseTestRunStart(testRunStartEventArgs); loggerEvents.WaitForEventCompletion(); - _mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestSourcesDiscovered, CommandLineOptions.Instance.Sources.Count()), OutputLevel.Information), Times.Once()); + _mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestSourcesDiscovered, _commandLineOptions.Sources.Count()), OutputLevel.Information), Times.Once()); } [TestMethod] @@ -1074,17 +1075,16 @@ public void TestRunStartHandlerShouldWriteTestSourcesDiscoveredOnConsoleIfVerbos loggerEvents.EnableEvents(); var fileHelper = new Mock(); - CommandLineOptions.Reset(); - CommandLineOptions.Instance.FileHelper = fileHelper.Object; - CommandLineOptions.Instance.FilePatternParser = new FilePatternParser(new Mock().Object, fileHelper.Object); + _commandLineOptions.FileHelper = fileHelper.Object; + _commandLineOptions.FilePatternParser = new FilePatternParser(new Mock().Object, fileHelper.Object); var temp = Path.GetTempPath(); string testFilePath = Path.Combine(temp, "DummyTestFile.dll"); fileHelper.Setup(fh => fh.Exists(testFilePath)).Returns(true); string testFilePath2 = Path.Combine(temp, "DummyTestFile2.dll"); fileHelper.Setup(fh => fh.Exists(testFilePath2)).Returns(true); - CommandLineOptions.Instance.AddSource(testFilePath); - CommandLineOptions.Instance.AddSource(testFilePath2); + _commandLineOptions.AddSource(testFilePath); + _commandLineOptions.AddSource(testFilePath2); var parameters = new Dictionary { @@ -1096,7 +1096,7 @@ public void TestRunStartHandlerShouldWriteTestSourcesDiscoveredOnConsoleIfVerbos loggerEvents.RaiseTestRunStart(testRunStartEventArgs); loggerEvents.WaitForEventCompletion(); - _mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestSourcesDiscovered, CommandLineOptions.Instance.Sources.Count()), OutputLevel.Information), Times.Once()); + _mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestSourcesDiscovered, _commandLineOptions.Sources.Count()), OutputLevel.Information), Times.Once()); _mockOutput.Verify(o => o.WriteLine(testFilePath, OutputLevel.Information), Times.Once); _mockOutput.Verify(o => o.WriteLine(testFilePath, OutputLevel.Information), Times.Once); } @@ -1108,17 +1108,16 @@ public void TestRunStartHandlerShouldNotWriteTestSourcesDiscoveredOnConsoleIfVer loggerEvents.EnableEvents(); var fileHelper = new Mock(); - CommandLineOptions.Reset(); - CommandLineOptions.Instance.FileHelper = fileHelper.Object; - CommandLineOptions.Instance.FilePatternParser = new FilePatternParser(new Mock().Object, fileHelper.Object); + _commandLineOptions.FileHelper = fileHelper.Object; + _commandLineOptions.FilePatternParser = new FilePatternParser(new Mock().Object, fileHelper.Object); var temp = Path.GetTempPath(); string testFilePath = Path.Combine(temp, "DummyTestFile.dll"); fileHelper.Setup(fh => fh.Exists(testFilePath)).Returns(true); string testFilePath2 = Path.Combine(temp, "DummyTestFile2.dll"); fileHelper.Setup(fh => fh.Exists(testFilePath2)).Returns(true); - CommandLineOptions.Instance.AddSource(testFilePath); - CommandLineOptions.Instance.AddSource(testFilePath2); + _commandLineOptions.AddSource(testFilePath); + _commandLineOptions.AddSource(testFilePath2); var parameters = new Dictionary { @@ -1130,7 +1129,7 @@ public void TestRunStartHandlerShouldNotWriteTestSourcesDiscoveredOnConsoleIfVer loggerEvents.RaiseTestRunStart(testRunStartEventArgs); loggerEvents.WaitForEventCompletion(); - _mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestSourcesDiscovered, CommandLineOptions.Instance.Sources.Count()), OutputLevel.Information), Times.Once()); + _mockOutput.Verify(o => o.WriteLine(string.Format(CultureInfo.CurrentCulture, CommandLineResources.TestSourcesDiscovered, _commandLineOptions.Sources.Count()), OutputLevel.Information), Times.Once()); _mockOutput.Verify(o => o.WriteLine(testFilePath, OutputLevel.Information), Times.Never); _mockOutput.Verify(o => o.WriteLine(testFilePath2, OutputLevel.Information), Times.Never); } From 2bd34e1f4535a205e5108e68f93aa0c227238868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Fri, 10 Jul 2026 12:12:42 +0200 Subject: [PATCH 53/87] Delete the TestRequestManager.Instance singleton (#16257) vstest.console built its TestRequestManager from a process-wide TestRequestManager.Instance singleton, and the six run/discovery argument processors fell back to it when no manager was injected. In design mode the same process serves many requests, so that shared instance carried state across them and made the processors hard to test in isolation. Executor now owns a single request manager and hands it to the processors through the factory. It is wrapped in a small LazyTestRequestManager so the real manager is still built lazily - it reads the parsed command line and loads the test platform, which has to happen after argument parsing - and commands that never run or discover tests (for example --Help) no longer construct one at all. The processors take the manager as a required dependency instead of reaching for the singleton, and TestRequestManager.Instance plus its parameterless constructor are gone. TestRequestManager is internal, so no external extension could read the singleton and nothing outside the composition root depended on it. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/vstest.console/CommandLine/Executor.cs | 11 ++-- ...istFullyQualifiedTestsArgumentProcessor.cs | 7 +- .../Processors/ListTestsArgumentProcessor.cs | 7 +- .../Processors/PortArgumentProcessor.cs | 7 +- .../RunSpecificTestsArgumentProcessor.cs | 7 +- .../Processors/RunTestsArgumentProcessor.cs | 7 +- .../UseVsixExtensionsArgumentProcessor.cs | 7 +- .../Utilities/ArgumentProcessorFactory.cs | 10 +-- .../LazyTestRequestManager.cs | 66 +++++++++++++++++++ .../TestPlatformHelpers/TestRequestManager.cs | 15 ----- ...llyQualifiedTestsArgumentProcessorTests.cs | 4 +- .../ListTestsArgumentProcessorTests.cs | 4 +- .../Processors/PortArgumentProcessorTests.cs | 4 +- .../RunSpecificTestsArgumentProcessorTests.cs | 4 +- .../RunTestsArgumentProcessorTests.cs | 4 +- ...UseVsixExtensionsArgumentProcessorTests.cs | 4 +- 16 files changed, 108 insertions(+), 60 deletions(-) create mode 100644 src/vstest.console/TestPlatformHelpers/LazyTestRequestManager.cs diff --git a/src/vstest.console/CommandLine/Executor.cs b/src/vstest.console/CommandLine/Executor.cs index 0d7b24453e..85a7f897c0 100644 --- a/src/vstest.console/CommandLine/Executor.cs +++ b/src/vstest.console/CommandLine/Executor.cs @@ -67,9 +67,10 @@ internal class Executor private readonly IRunSettingsHelper _runSettingsHelper; private readonly CommandLineOptions _commandLineOptions; private readonly TestRunResultAggregator _testRunResultAggregator; - // Left null in production so the argument processors resolve TestRequestManager.Instance lazily - // (only when a run/discovery command actually executes); tests inject a specific instance. - private readonly ITestRequestManager? _testRequestManager; + // The single request manager for this Executor. It is built lazily (the real manager reads the + // parsed command line and loads the test platform, which must happen after argument parsing), so + // commands that never run or discover tests (for example --Help) never construct it. + private readonly ITestRequestManager _testRequestManager; private bool _showHelp; /// @@ -124,7 +125,7 @@ internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSour _runSettingsHelper = runSettingsHelper; _commandLineOptions = commandLineOptions; _testRunResultAggregator = testRunResultAggregator; - _testRequestManager = testRequestManager; + _testRequestManager = testRequestManager ?? new LazyTestRequestManager(() => new TestRequestManager(_commandLineOptions)); } /// @@ -230,7 +231,7 @@ internal int Execute(params string[]? args) _testPlatformEventSource.MetricsDisposeStart(); // Disposing Metrics Publisher when VsTestConsole ends - TestRequestManager.Instance.Dispose(); + _testRequestManager.Dispose(); _testPlatformEventSource.MetricsDisposeStop(); return exitCode; diff --git a/src/vstest.console/Processors/ListFullyQualifiedTestsArgumentProcessor.cs b/src/vstest.console/Processors/ListFullyQualifiedTestsArgumentProcessor.cs index 2f9734850f..77dbc66765 100644 --- a/src/vstest.console/Processors/ListFullyQualifiedTestsArgumentProcessor.cs +++ b/src/vstest.console/Processors/ListFullyQualifiedTestsArgumentProcessor.cs @@ -8,7 +8,6 @@ using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; using Microsoft.VisualStudio.TestPlatform.CommandLine.Internal; -using Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers; using Microsoft.VisualStudio.TestPlatform.Common; using Microsoft.VisualStudio.TestPlatform.Common.Filtering; using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; @@ -34,9 +33,9 @@ internal class ListFullyQualifiedTestsArgumentProcessor : IArgumentProcessor private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; private readonly CommandLineOptions _commandLineOptions; - private readonly ITestRequestManager? _testRequestManager; + private readonly ITestRequestManager _testRequestManager; - public ListFullyQualifiedTestsArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider, ITestRequestManager? testRequestManager = null) + public ListFullyQualifiedTestsArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider, ITestRequestManager testRequestManager) { _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; @@ -59,7 +58,7 @@ public Lazy? Executor new ListFullyQualifiedTestsArgumentExecutor( _commandLineOptions, _runSettingsProvider, - _testRequestManager ?? TestRequestManager.Instance)); + _testRequestManager)); set => _executor = value; } diff --git a/src/vstest.console/Processors/ListTestsArgumentProcessor.cs b/src/vstest.console/Processors/ListTestsArgumentProcessor.cs index 546471583e..6bab8040dc 100644 --- a/src/vstest.console/Processors/ListTestsArgumentProcessor.cs +++ b/src/vstest.console/Processors/ListTestsArgumentProcessor.cs @@ -7,7 +7,6 @@ using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; using Microsoft.VisualStudio.TestPlatform.CommandLine.Internal; -using Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers; using Microsoft.VisualStudio.TestPlatform.Common; using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; @@ -37,9 +36,9 @@ internal class ListTestsArgumentProcessor : IArgumentProcessor private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; private readonly CommandLineOptions _commandLineOptions; - private readonly ITestRequestManager? _testRequestManager; + private readonly ITestRequestManager _testRequestManager; - public ListTestsArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider, ITestRequestManager? testRequestManager = null) + public ListTestsArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider, ITestRequestManager testRequestManager) { _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; @@ -62,7 +61,7 @@ public Lazy? Executor new ListTestsArgumentExecutor( _commandLineOptions, _runSettingsProvider, - _testRequestManager ?? TestRequestManager.Instance)); + _testRequestManager)); set => _executor = value; } diff --git a/src/vstest.console/Processors/PortArgumentProcessor.cs b/src/vstest.console/Processors/PortArgumentProcessor.cs index 2f4b62ac68..66d9c8bf8a 100644 --- a/src/vstest.console/Processors/PortArgumentProcessor.cs +++ b/src/vstest.console/Processors/PortArgumentProcessor.cs @@ -8,7 +8,6 @@ using Microsoft.VisualStudio.TestPlatform.Client.DesignMode; using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; -using Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Abstraction::Microsoft.VisualStudio.TestPlatform.PlatformAbstractions; using Abstraction::Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; @@ -32,9 +31,9 @@ internal class PortArgumentProcessor : IArgumentProcessor private Lazy? _executor; private readonly IRunSettingsHelper _runSettingsHelper; private readonly CommandLineOptions _commandLineOptions; - private readonly ITestRequestManager? _testRequestManager; + private readonly ITestRequestManager _testRequestManager; - public PortArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsHelper runSettingsHelper, ITestRequestManager? testRequestManager = null) + public PortArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsHelper runSettingsHelper, ITestRequestManager testRequestManager) { _commandLineOptions = commandLineOptions; _runSettingsHelper = runSettingsHelper; @@ -53,7 +52,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new PortArgumentExecutor(_commandLineOptions, _testRequestManager ?? TestRequestManager.Instance, _runSettingsHelper)); + new PortArgumentExecutor(_commandLineOptions, _testRequestManager, _runSettingsHelper)); set => _executor = value; } diff --git a/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs b/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs index e311c673f5..59a5b4477d 100644 --- a/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs +++ b/src/vstest.console/Processors/RunSpecificTestsArgumentProcessor.cs @@ -10,7 +10,6 @@ using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; using Microsoft.VisualStudio.TestPlatform.CommandLine.Internal; -using Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers; using Microsoft.VisualStudio.TestPlatform.Common; using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; using Microsoft.VisualStudio.TestPlatform.Common.Utilities; @@ -32,9 +31,9 @@ internal class RunSpecificTestsArgumentProcessor : IArgumentProcessor private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; private readonly CommandLineOptions _commandLineOptions; - private readonly ITestRequestManager? _testRequestManager; + private readonly ITestRequestManager _testRequestManager; - public RunSpecificTestsArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider, ITestRequestManager? testRequestManager = null) + public RunSpecificTestsArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider, ITestRequestManager testRequestManager) { _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; @@ -51,7 +50,7 @@ public Lazy? Executor new RunSpecificTestsArgumentExecutor( _commandLineOptions, _runSettingsProvider, - _testRequestManager ?? TestRequestManager.Instance, + _testRequestManager, new ArtifactProcessingManager(_commandLineOptions.TestSessionCorrelationId), ConsoleOutput.Instance)); diff --git a/src/vstest.console/Processors/RunTestsArgumentProcessor.cs b/src/vstest.console/Processors/RunTestsArgumentProcessor.cs index d696a8a158..6f27266ec3 100644 --- a/src/vstest.console/Processors/RunTestsArgumentProcessor.cs +++ b/src/vstest.console/Processors/RunTestsArgumentProcessor.cs @@ -6,7 +6,6 @@ using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; using Microsoft.VisualStudio.TestPlatform.CommandLine.Internal; -using Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers; using Microsoft.VisualStudio.TestPlatform.Common; using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; using Microsoft.VisualStudio.TestPlatform.Common.Utilities; @@ -28,9 +27,9 @@ internal class RunTestsArgumentProcessor : IArgumentProcessor private Lazy? _executor; private readonly IRunSettingsProvider _runSettingsProvider; private readonly CommandLineOptions _commandLineOptions; - private readonly ITestRequestManager? _testRequestManager; + private readonly ITestRequestManager _testRequestManager; - public RunTestsArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider, ITestRequestManager? testRequestManager = null) + public RunTestsArgumentProcessor(CommandLineOptions commandLineOptions, IRunSettingsProvider runSettingsProvider, ITestRequestManager testRequestManager) { _commandLineOptions = commandLineOptions; _runSettingsProvider = runSettingsProvider; @@ -47,7 +46,7 @@ public Lazy? Executor new RunTestsArgumentExecutor( _commandLineOptions, _runSettingsProvider, - _testRequestManager ?? TestRequestManager.Instance, + _testRequestManager, new ArtifactProcessingManager(_commandLineOptions.TestSessionCorrelationId), ConsoleOutput.Instance)); diff --git a/src/vstest.console/Processors/UseVsixExtensionsArgumentProcessor.cs b/src/vstest.console/Processors/UseVsixExtensionsArgumentProcessor.cs index 0befc28427..7da0c543da 100644 --- a/src/vstest.console/Processors/UseVsixExtensionsArgumentProcessor.cs +++ b/src/vstest.console/Processors/UseVsixExtensionsArgumentProcessor.cs @@ -5,7 +5,6 @@ using System.Globalization; using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; -using Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers; using Microsoft.VisualStudio.TestPlatform.Common.ExtensionFramework; using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; using Microsoft.VisualStudio.TestPlatform.Utilities; @@ -27,9 +26,9 @@ internal class UseVsixExtensionsArgumentProcessor : IArgumentProcessor private Lazy? _metadata; private Lazy? _executor; private readonly CommandLineOptions _commandLineOptions; - private readonly ITestRequestManager? _testRequestManager; + private readonly ITestRequestManager _testRequestManager; - public UseVsixExtensionsArgumentProcessor(CommandLineOptions commandLineOptions, ITestRequestManager? testRequestManager = null) + public UseVsixExtensionsArgumentProcessor(CommandLineOptions commandLineOptions, ITestRequestManager testRequestManager) { _commandLineOptions = commandLineOptions; _testRequestManager = testRequestManager; @@ -48,7 +47,7 @@ public Lazy Metadata public Lazy? Executor { get => _executor ??= new Lazy(() => - new UseVsixExtensionsArgumentExecutor(_commandLineOptions, _testRequestManager ?? TestRequestManager.Instance, new VSExtensionManager(), ConsoleOutput.Instance)); + new UseVsixExtensionsArgumentExecutor(_commandLineOptions, _testRequestManager, new VSExtensionManager(), ConsoleOutput.Instance)); set => _executor = value; } diff --git a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs index 0fadcfbeeb..b979e8ba09 100644 --- a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs +++ b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs @@ -8,6 +8,7 @@ using System.Linq; using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; +using Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers; using Microsoft.VisualStudio.TestPlatform.Common; using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; using Microsoft.VisualStudio.TestPlatform.ObjectModel; @@ -66,9 +67,9 @@ protected ArgumentProcessorFactory(IEnumerable argumentProce /// /// /// The test request manager that the run/discovery argument processors hand to their executors. - /// When not provided the processors fall back to the ambient - /// lazily, at the point the executor is built, so that commands that never touch it (for example - /// --Help) do not force its (relatively heavy) construction. + /// When not provided a request-scoped manager is created lazily, at the point the executor is + /// built, so that commands that never touch it (for example --Help) do not force its + /// (relatively heavy) construction. /// /// ArgumentProcessorFactory. internal static ArgumentProcessorFactory Create(IFeatureFlag? featureFlag = null, IRunSettingsProvider? runSettingsProvider = null, IRunSettingsHelper? runSettingsHelper = null, CommandLineOptions? commandLineOptions = null, ITestRequestManager? testRequestManager = null) @@ -76,6 +77,7 @@ internal static ArgumentProcessorFactory Create(IFeatureFlag? featureFlag = null runSettingsProvider ??= RunSettingsManager.Instance; runSettingsHelper ??= RunSettingsHelper.Instance; commandLineOptions ??= CommandLineOptions.Instance; + testRequestManager ??= new LazyTestRequestManager(() => new TestRequestManager(commandLineOptions)); var defaultArgumentProcessor = GetDefaultArgumentProcessors(runSettingsProvider, runSettingsHelper, commandLineOptions, testRequestManager); if (!(featureFlag ?? FeatureFlag.Instance).IsSet(FeatureFlag.VSTEST_DISABLE_ARTIFACTS_POSTPROCESSING)) @@ -210,7 +212,7 @@ public IEnumerable GetArgumentProcessorsToAlwaysExecute() .Where(lazyProcessor => lazyProcessor.Metadata.Value.IsSpecialCommand && lazyProcessor.Metadata.Value.AlwaysExecute); } - private static IList GetDefaultArgumentProcessors(IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper, CommandLineOptions commandLineOptions, ITestRequestManager? testRequestManager) => new List { + private static IList GetDefaultArgumentProcessors(IRunSettingsProvider runSettingsProvider, IRunSettingsHelper runSettingsHelper, CommandLineOptions commandLineOptions, ITestRequestManager testRequestManager) => new List { new HelpArgumentProcessor(), new TestSourceArgumentProcessor(commandLineOptions), new ListTestsArgumentProcessor(commandLineOptions, runSettingsProvider, testRequestManager), diff --git a/src/vstest.console/TestPlatformHelpers/LazyTestRequestManager.cs b/src/vstest.console/TestPlatformHelpers/LazyTestRequestManager.cs new file mode 100644 index 0000000000..19494bc059 --- /dev/null +++ b/src/vstest.console/TestPlatformHelpers/LazyTestRequestManager.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; + +using Microsoft.VisualStudio.TestPlatform.Client.RequestHelper; +using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client.Interfaces; + +namespace Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers; + +/// +/// An that defers construction of the real +/// until the first member is used. This lets the composition root +/// own a single request manager instead of reaching for a process-wide singleton, while still +/// building it lazily: the real manager reads the parsed command line (for example +/// IsDesignMode) and loads the test platform, so it must be created only after the command +/// line has been parsed, and never for commands that neither run nor discover tests (for example +/// --Help). +/// +internal sealed class LazyTestRequestManager : ITestRequestManager +{ + private readonly Lazy _inner; + + public LazyTestRequestManager(Func factory) + { + _inner = new Lazy(factory); + } + + public void InitializeExtensions(IEnumerable? pathToAdditionalExtensions, bool skipExtensionFilters) + => _inner.Value.InitializeExtensions(pathToAdditionalExtensions, skipExtensionFilters); + + public void ResetOptions() + => _inner.Value.ResetOptions(); + + public void DiscoverTests(DiscoveryRequestPayload discoveryPayload, ITestDiscoveryEventsRegistrar disoveryEventsRegistrar, ProtocolConfig protocolConfig) + => _inner.Value.DiscoverTests(discoveryPayload, disoveryEventsRegistrar, protocolConfig); + + public void RunTests(TestRunRequestPayload testRunRequestPayLoad, ITestHostLauncher3? customTestHostLauncher, ITestRunEventsRegistrar testRunEventsRegistrar, ProtocolConfig protocolConfig) + => _inner.Value.RunTests(testRunRequestPayLoad, customTestHostLauncher, testRunEventsRegistrar, protocolConfig); + + public void ProcessTestRunAttachments(TestRunAttachmentsProcessingPayload testRunAttachmentsProcessingPayload, ITestRunAttachmentsProcessingEventsHandler testRunAttachmentsProcessingEventsHandler, ProtocolConfig protocolConfig) + => _inner.Value.ProcessTestRunAttachments(testRunAttachmentsProcessingPayload, testRunAttachmentsProcessingEventsHandler, protocolConfig); + + public void CancelTestRun() + => _inner.Value.CancelTestRun(); + + public void AbortTestRun() + => _inner.Value.AbortTestRun(); + + public void CancelDiscovery() + => _inner.Value.CancelDiscovery(); + + public void CancelTestRunAttachmentsProcessing() + => _inner.Value.CancelTestRunAttachmentsProcessing(); + + public void Dispose() + { + if (_inner.IsValueCreated) + { + _inner.Value.Dispose(); + } + } +} diff --git a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs index 7685c57683..06d67535c0 100644 --- a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs +++ b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs @@ -47,8 +47,6 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.TestPlatformHelpers; /// internal class TestRequestManager : ITestRequestManager { - private static ITestRequestManager? s_testRequestManagerInstance; - private readonly ITestPlatform _testPlatform; private readonly ITestPlatformEventSource _testPlatformEventSource; // TODO: No idea what is Task supposed to buy us, Tasks start immediately on instantiation @@ -92,13 +90,6 @@ internal class TestRequestManager : ITestRequestManager /// private CancellationTokenSource? _currentAttachmentsProcessingCancellationTokenSource; - /// - /// Initializes a new instance of the class. - /// - public TestRequestManager() - : this(CommandLineOptions.Instance) - { - } internal TestRequestManager(CommandLineOptions commandLineOptions) : this( @@ -170,12 +161,6 @@ internal TestRequestManager( _runSettingsHelper = runSettingsHelper; } - /// - /// Gets the test request manager instance. - /// - public static ITestRequestManager Instance - => s_testRequestManagerInstance ??= new TestRequestManager(); - #region ITestRequestManager /// diff --git a/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs index 7cd2d85651..7e2db70211 100644 --- a/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs @@ -95,7 +95,7 @@ public ListFullyQualifiedTestsArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnListFullyQualifiedTestsArgumentProcessorCapabilities() { - var processor = new ListFullyQualifiedTestsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); + var processor = new ListFullyQualifiedTestsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider(), new Mock().Object); Assert.IsTrue(processor.Metadata.Value is ListFullyQualifiedTestsArgumentProcessorCapabilities); } @@ -105,7 +105,7 @@ public void GetMetadataShouldReturnListFullyQualifiedTestsArgumentProcessorCapab [TestMethod] public void GetExecuterShouldReturnListFullyQualifiedTestsArgumentProcessorCapabilities() { - var processor = new ListFullyQualifiedTestsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); + var processor = new ListFullyQualifiedTestsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider(), new Mock().Object); Assert.IsTrue(processor.Executor!.Value is ListFullyQualifiedTestsArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs index 735ddb1a1a..a894fd7b66 100644 --- a/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs @@ -93,7 +93,7 @@ public ListTestsArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnListTestsArgumentProcessorCapabilities() { - var processor = new ListTestsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); + var processor = new ListTestsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider(), new Mock().Object); Assert.IsTrue(processor.Metadata.Value is ListTestsArgumentProcessorCapabilities); } @@ -103,7 +103,7 @@ public void GetMetadataShouldReturnListTestsArgumentProcessorCapabilities() [TestMethod] public void GetExecuterShouldReturnListTestsArgumentProcessorCapabilities() { - var processor = new ListTestsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); + var processor = new ListTestsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider(), new Mock().Object); Assert.IsTrue(processor.Executor!.Value is ListTestsArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs index 0208a8387c..836582b19e 100644 --- a/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs @@ -40,14 +40,14 @@ public PortArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnPortArgumentProcessorCapabilities() { - var processor = new PortArgumentProcessor(CommandLineOptions.Instance, _runSettingsHelper); + var processor = new PortArgumentProcessor(CommandLineOptions.Instance, _runSettingsHelper, _testRequestManager.Object); Assert.IsTrue(processor.Metadata.Value is PortArgumentProcessorCapabilities); } [TestMethod] public void GetExecutorShouldReturnPortArgumentProcessorCapabilities() { - var processor = new PortArgumentProcessor(CommandLineOptions.Instance, _runSettingsHelper); + var processor = new PortArgumentProcessor(CommandLineOptions.Instance, _runSettingsHelper, _testRequestManager.Object); Assert.IsTrue(processor.Executor!.Value is PortArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs index 1a0a2848bf..ad671bb77f 100644 --- a/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs @@ -81,7 +81,7 @@ public RunSpecificTestsArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnRunSpecificTestsArgumentProcessorCapabilities() { - RunSpecificTestsArgumentProcessor processor = new(CommandLineOptions.Instance, new TestableRunSettingsProvider()); + RunSpecificTestsArgumentProcessor processor = new(CommandLineOptions.Instance, new TestableRunSettingsProvider(), new Mock().Object); Assert.IsTrue(processor.Metadata.Value is RunSpecificTestsArgumentProcessorCapabilities); } @@ -89,7 +89,7 @@ public void GetMetadataShouldReturnRunSpecificTestsArgumentProcessorCapabilities [TestMethod] public void GetExecutorShouldReturnRunSpecificTestsArgumentExecutor() { - RunSpecificTestsArgumentProcessor processor = new(CommandLineOptions.Instance, new TestableRunSettingsProvider()); + RunSpecificTestsArgumentProcessor processor = new(CommandLineOptions.Instance, new TestableRunSettingsProvider(), new Mock().Object); Assert.IsTrue(processor.Executor!.Value is RunSpecificTestsArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs index 5e95a262e6..c91e6c7849 100644 --- a/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs @@ -79,14 +79,14 @@ public RunTestsArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnRunTestsArgumentProcessorCapabilities() { - RunTestsArgumentProcessor processor = new(CommandLineOptions.Instance, new TestableRunSettingsProvider()); + RunTestsArgumentProcessor processor = new(CommandLineOptions.Instance, new TestableRunSettingsProvider(), new Mock().Object); Assert.IsTrue(processor.Metadata.Value is RunTestsArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnRunTestsArgumentProcessorCapabilities() { - RunTestsArgumentProcessor processor = new(CommandLineOptions.Instance, new TestableRunSettingsProvider()); + RunTestsArgumentProcessor processor = new(CommandLineOptions.Instance, new TestableRunSettingsProvider(), new Mock().Object); Assert.IsTrue(processor.Executor!.Value is RunTestsArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/UseVsixExtensionsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/UseVsixExtensionsArgumentProcessorTests.cs index 66c096e5cc..de13427eac 100644 --- a/test/vstest.console.UnitTests/Processors/UseVsixExtensionsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/UseVsixExtensionsArgumentProcessorTests.cs @@ -34,14 +34,14 @@ public UseVsixExtensionsArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnUseVsixExtensionsArgumentProcessorCapabilities() { - var processor = new UseVsixExtensionsArgumentProcessor(CommandLineOptions.Instance); + var processor = new UseVsixExtensionsArgumentProcessor(CommandLineOptions.Instance, _testRequestManager.Object); Assert.IsTrue(processor.Metadata.Value is UseVsixExtensionsArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnUseVsixExtensionsArgumentProcessorCapabilities() { - var processor = new UseVsixExtensionsArgumentProcessor(CommandLineOptions.Instance); + var processor = new UseVsixExtensionsArgumentProcessor(CommandLineOptions.Instance, _testRequestManager.Object); Assert.IsTrue(processor.Executor!.Value is UseVsixExtensionsArgumentExecutor); } From d97e6847da1bf72505b0237080e71bc660332d48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Fri, 10 Jul 2026 15:32:21 +0200 Subject: [PATCH 54/87] Build command line options at the composition root instead of the CommandLineOptions.Instance singleton (#16258) * Build command line options at the composition root instead of the CommandLineOptions.Instance singleton The Executor seeded its CommandLineOptions from the process-wide CommandLineOptions.Instance singleton, and ArgumentProcessorFactory.Create fell back to the same singleton when no options were passed. In design mode one process serves many command lines, so the parsed options leaked from one request into the next. The Executor now creates its own CommandLineOptions and threads that single instance to the argument processors through the factory, which is what the run path already relied on - Executor passes its instance to the factory, and the only other caller, the help processor, just needs some options to list the processors for --Help. The factory's fallback now builds a fresh instance instead of reaching for the singleton. CommandLineOptions is internal, so nothing outside vstest.console could read the singleton. It stays for now only because the unit tests still use it directly; a follow-up moves those over and removes CommandLineOptions.Instance for good. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Retrigger CI (flaky integration-test timeout) The Windows leg timed out at 120 min after the Integration Test step hung following a skipped, CI-flaky CancelTestDiscovery, and the source-line DifferentTestFrameworkSimpleTests reported Line 0 for both the NUnit and xUnit adapters - a global source-information gap on that agent, unrelated to the command-line-options change in this PR. Empty commit to re-run. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/vstest.console/CommandLine/Executor.cs | 4 ++-- .../Processors/Utilities/ArgumentProcessorFactory.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vstest.console/CommandLine/Executor.cs b/src/vstest.console/CommandLine/Executor.cs index 85a7f897c0..12b69a9c4d 100644 --- a/src/vstest.console/CommandLine/Executor.cs +++ b/src/vstest.console/CommandLine/Executor.cs @@ -101,12 +101,12 @@ internal class Executor } internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSource, IProcessHelper processHelper, IEnvironment environment) - : this(output, testPlatformEventSource, processHelper, environment, RunSettingsManager.Instance, RunSettingsHelper.Instance, CommandLineOptions.Instance, TestRunResultAggregator.Instance) + : this(output, testPlatformEventSource, processHelper, environment, RunSettingsManager.Instance, RunSettingsHelper.Instance, new CommandLineOptions(), TestRunResultAggregator.Instance) { } internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSource, IProcessHelper processHelper, IEnvironment environment, IRunSettingsProvider runSettingsProvider) - : this(output, testPlatformEventSource, processHelper, environment, runSettingsProvider, RunSettingsHelper.Instance, CommandLineOptions.Instance, TestRunResultAggregator.Instance) + : this(output, testPlatformEventSource, processHelper, environment, runSettingsProvider, RunSettingsHelper.Instance, new CommandLineOptions(), TestRunResultAggregator.Instance) { } diff --git a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs index b979e8ba09..5eaf092148 100644 --- a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs +++ b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs @@ -62,8 +62,8 @@ protected ArgumentProcessorFactory(IEnumerable argumentProce /// /// /// The command line options that the created argument processors read from and write to. - /// Defaults to the ambient when not provided, so that - /// callers (and the composition root) can inject an isolated instance instead of sharing static state. + /// When not provided a fresh, request-scoped instance is created, so that callers never + /// share command line state through a static singleton. /// /// /// The test request manager that the run/discovery argument processors hand to their executors. @@ -76,7 +76,7 @@ internal static ArgumentProcessorFactory Create(IFeatureFlag? featureFlag = null { runSettingsProvider ??= RunSettingsManager.Instance; runSettingsHelper ??= RunSettingsHelper.Instance; - commandLineOptions ??= CommandLineOptions.Instance; + commandLineOptions ??= new CommandLineOptions(); testRequestManager ??= new LazyTestRequestManager(() => new TestRequestManager(commandLineOptions)); var defaultArgumentProcessor = GetDefaultArgumentProcessors(runSettingsProvider, runSettingsHelper, commandLineOptions, testRequestManager); From 12ff13d177320559fbb8d684ef6c8bccc3bfb998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Fri, 10 Jul 2026 16:12:50 +0200 Subject: [PATCH 55/87] Delete the CommandLineOptions.Instance singleton (#16261) Production stopped reading the static in the two prior steps: Executor and the argument-processor factory build a request-scoped CommandLineOptions and thread it into every processor, and TestRequestManager takes it by constructor. Nothing in src reads CommandLineOptions.Instance anymore, so the singleton is only test scaffolding now. ResetOptions() used to null the singleton so the next design-mode request rebuilt defaults. The manager now holds the options it was constructed with and never mutates them per request - design-mode requests derive their state from the payload (sources, run settings), not from shared command-line options - so ResetOptions() is a no-op and the static Reset() is removed. CommandLineOptions is internal, so no third-party extension can see it and there is no compatibility surface to keep. Tests build their own CommandLineOptions per test class instead of poking a shared static, which also drops the Reset() calls they needed for isolation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../CommandLine/CommandLineOptions.cs | 16 ---- src/vstest.console/Internal/ConsoleLogger.cs | 4 +- .../TestPlatformHelpers/TestRequestManager.cs | 5 +- .../CommandLine/CommandLineOptionsTests.cs | 30 +++--- .../GenerateFakesUtilitiesTests.cs | 12 +-- .../ExecutorUnitTests.cs | 5 +- .../Internal/ConsoleLoggerTests.cs | 11 +-- .../CLIRunSettingsArgumentProcessorTests.cs | 7 +- .../DisableAutoFakesArgumentProcessorTests.cs | 7 +- ...nableCodeCoverageArgumentProcessorTests.cs | 11 ++- .../EnvironmentArgumentProcessorTests.cs | 5 +- .../FrameworkArgumentProcessorTests.cs | 18 ++-- .../InIsolationArgumentProcessorTests.cs | 12 +-- ...llyQualifiedTestsArgumentProcessorTests.cs | 42 ++++----- .../ListTestsArgumentProcessorTests.cs | 38 ++++---- ...stTestsTargetPathArgumentProcessorTests.cs | 11 ++- .../ParallelArgumentProcessorTests.cs | 10 +- .../ParentProcessIdArgumentProcessorTests.cs | 15 +-- .../PlatformArgumentProcessorTests.cs | 12 +-- .../Processors/PortArgumentProcessorTests.cs | 27 +++--- .../ResponseFileArgumentProcessorTests.cs | 6 -- .../ResultsDirectoryArgumentProcessorTests.cs | 12 +-- .../RunSettingsArgumentProcessorTests.cs | 56 ++++++------ .../RunSpecificTestsArgumentProcessorTests.cs | 70 +++++++------- .../RunTestsArgumentProcessorTests.cs | 34 ++++--- ...erLoadingStrategyArgumentProcessorTests.cs | 7 +- .../TestAdapterPathArgumentProcessorTests.cs | 15 +-- .../TestCaseFilterArgumentProcessorTests.cs | 13 +-- .../TestSourceArgumentProcessorTests.cs | 12 +-- ...UseVsixExtensionsArgumentProcessorTests.cs | 9 +- .../ArgumentProcessorFactoryTests.cs | 2 +- .../TestRequestManagerTests.cs | 91 +++++++++---------- 32 files changed, 300 insertions(+), 325 deletions(-) diff --git a/src/vstest.console/CommandLine/CommandLineOptions.cs b/src/vstest.console/CommandLine/CommandLineOptions.cs index a6ea07be1f..15d2753386 100644 --- a/src/vstest.console/CommandLine/CommandLineOptions.cs +++ b/src/vstest.console/CommandLine/CommandLineOptions.cs @@ -44,20 +44,12 @@ internal class CommandLineOptions /// private readonly TimeSpan _defaultRetrievalTimeout = new(0, 0, 0, 1, 500); - private static CommandLineOptions? s_instance; - private List _sources = new(); private Architecture _architecture; private Framework? _frameworkVersion; - /// - /// Gets the instance. - /// - internal static CommandLineOptions Instance - => s_instance ??= new CommandLineOptions(); - /// /// Default constructor. /// @@ -292,12 +284,4 @@ public void AddSource(string source) _sources = _sources.Union(filteredFiles).ToList(); } - /// - /// Resets the options. Clears the sources. - /// - internal static void Reset() - { - s_instance = null; - } - } diff --git a/src/vstest.console/Internal/ConsoleLogger.cs b/src/vstest.console/Internal/ConsoleLogger.cs index e76a3931c5..8afe663421 100644 --- a/src/vstest.console/Internal/ConsoleLogger.cs +++ b/src/vstest.console/Internal/ConsoleLogger.cs @@ -131,8 +131,8 @@ internal ConsoleLogger(IOutput output, IProgressIndicator progressIndicator, IFe Output = output; _progressIndicator = progressIndicator; _featureFlag = featureFlag; - // Never fall back to CommandLineOptions.Instance: the logger owns its own options so tests - // stay isolated and the built-in logger no longer reads the process-wide singleton. + // The logger owns its own options so tests stay isolated and the built-in logger never reads a + // process-wide singleton (there is none): callers inject the options, tests get a fresh instance. _commandLineOptions = commandLineOptions ?? new CommandLineOptions(); } diff --git a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs index 06d67535c0..d1ded4836e 100644 --- a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs +++ b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs @@ -180,7 +180,10 @@ public void InitializeExtensions( /// public void ResetOptions() { - CommandLineOptions.Reset(); + // Nothing to reset. The manager holds the CommandLineOptions it was constructed with and + // never mutates them per request; design-mode requests derive their state from the request + // payload (sources, run settings), not from shared mutable command-line options. This used + // to null a process-wide CommandLineOptions singleton, which no longer exists. } /// diff --git a/test/vstest.console.UnitTests/CommandLine/CommandLineOptionsTests.cs b/test/vstest.console.UnitTests/CommandLine/CommandLineOptionsTests.cs index 92c11edcf4..2c7cde6e40 100644 --- a/test/vstest.console.UnitTests/CommandLine/CommandLineOptionsTests.cs +++ b/test/vstest.console.UnitTests/CommandLine/CommandLineOptionsTests.cs @@ -19,6 +19,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.CommandLine; [TestClass] public class CommandLineOptionsTests { + private readonly CommandLineOptions _commandLineOptions = new(); private readonly Mock _fileHelper; private readonly FilePatternParser _filePatternParser; private readonly string _currentDirectory = @"C:\\Temp"; @@ -27,16 +28,15 @@ public CommandLineOptionsTests() { _fileHelper = new Mock(); _filePatternParser = new FilePatternParser(new Mock().Object, _fileHelper.Object); - CommandLineOptions.Reset(); - CommandLineOptions.Instance.FileHelper = _fileHelper.Object; - CommandLineOptions.Instance.FilePatternParser = _filePatternParser; + _commandLineOptions.FileHelper = _fileHelper.Object; + _commandLineOptions.FilePatternParser = _filePatternParser; _fileHelper.Setup(fh => fh.GetCurrentDirectory()).Returns(_currentDirectory); } [TestMethod] public void CommandLineOptionsDefaultBatchSizeIsTen() { - Assert.AreEqual(10, CommandLineOptions.Instance.BatchSize); + Assert.AreEqual(10, _commandLineOptions.BatchSize); } [TestMethod] @@ -51,29 +51,29 @@ public void CommandLineOptionsDiscoveryDefaultBatchSizeIsThousand() public void CommandLineOptionsDefaultTestRunStatsEventTimeoutIsOnePointFiveSec() { var timeout = new TimeSpan(0, 0, 0, 1, 500); - Assert.AreEqual(timeout, CommandLineOptions.Instance.TestStatsEventTimeout); + Assert.AreEqual(timeout, _commandLineOptions.TestStatsEventTimeout); } [TestMethod] public void CommandLineOptionsGetForSourcesPropertyShouldReturnReadonlySourcesEnumerable() { - Assert.IsTrue(CommandLineOptions.Instance.Sources is ReadOnlyCollection); + Assert.IsTrue(_commandLineOptions.Sources is ReadOnlyCollection); } [TestMethod] public void CommandLineOptionsGetForHasPhoneContextPropertyIfTargetDeviceIsSetReturnsTrue() { - Assert.IsFalse(CommandLineOptions.Instance.HasPhoneContext); + Assert.IsFalse(_commandLineOptions.HasPhoneContext); // Set some not null value - CommandLineOptions.Instance.TargetDevice = "TargetDevice"; - Assert.IsTrue(CommandLineOptions.Instance.HasPhoneContext); + _commandLineOptions.TargetDevice = "TargetDevice"; + Assert.IsTrue(_commandLineOptions.HasPhoneContext); } [TestMethod] public void CommandLineOptionsAddSourceShouldThrowCommandLineExceptionForNullSource() { - Assert.ThrowsExactly(() => CommandLineOptions.Instance.AddSource(null!)); + Assert.ThrowsExactly(() => _commandLineOptions.AddSource(null!)); } [TestMethod] @@ -84,14 +84,14 @@ public void CommandLineOptionsAddSourceShouldConvertRelativePathToAbsolutePath() _fileHelper.Setup(fh => fh.Exists(absolutePath)).Returns(true); // Pass relative path - CommandLineOptions.Instance.AddSource(relativeTestFilePath); - Assert.IsTrue(CommandLineOptions.Instance.Sources.Contains(absolutePath)); + _commandLineOptions.AddSource(relativeTestFilePath); + Assert.IsTrue(_commandLineOptions.Sources.Contains(absolutePath)); } [TestMethod] public void CommandLineOptionsAddSourceShouldThrowCommandLineExceptionForInvalidSource() { - Assert.ThrowsExactly(() => CommandLineOptions.Instance.AddSource("DummySource")); + Assert.ThrowsExactly(() => _commandLineOptions.AddSource("DummySource")); } [TestMethod] @@ -100,8 +100,8 @@ public void CommandLineOptionsAddSourceShouldAddSourceForValidSource() string testFilePath = Path.Combine(Path.GetTempPath(), "DummyTestFile.txt"); _fileHelper.Setup(fh => fh.Exists(testFilePath)).Returns(true); - CommandLineOptions.Instance.AddSource(testFilePath); + _commandLineOptions.AddSource(testFilePath); - Assert.IsTrue(CommandLineOptions.Instance.Sources.Contains(testFilePath)); + Assert.IsTrue(_commandLineOptions.Sources.Contains(testFilePath)); } } diff --git a/test/vstest.console.UnitTests/CommandLine/GenerateFakesUtilitiesTests.cs b/test/vstest.console.UnitTests/CommandLine/GenerateFakesUtilitiesTests.cs index 201ee8312c..e49f639f82 100644 --- a/test/vstest.console.UnitTests/CommandLine/GenerateFakesUtilitiesTests.cs +++ b/test/vstest.console.UnitTests/CommandLine/GenerateFakesUtilitiesTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.VisualStudio.TestPlatform.CommandLineUtilities; @@ -12,6 +12,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.CommandLine; [TestClass] public class GenerateFakesUtilitiesTests { + private readonly CommandLineOptions _commandLineOptions = new(); private readonly Mock _fileHelper; private readonly string _currentDirectory = @"C:\\Temp"; private readonly string _runSettings = string.Empty; @@ -19,8 +20,7 @@ public class GenerateFakesUtilitiesTests public GenerateFakesUtilitiesTests() { _fileHelper = new Mock(); - CommandLineOptions.Reset(); - CommandLineOptions.Instance.FileHelper = _fileHelper.Object; + _commandLineOptions.FileHelper = _fileHelper.Object; _fileHelper.Setup(fh => fh.GetCurrentDirectory()).Returns(_currentDirectory); _runSettings = @".netstandard,Version=5.0"; } @@ -28,16 +28,16 @@ public GenerateFakesUtilitiesTests() [TestMethod] public void CommandLineOptionsDefaultDisableAutoFakesIsFalse() { - Assert.IsFalse(CommandLineOptions.Instance.DisableAutoFakes); + Assert.IsFalse(_commandLineOptions.DisableAutoFakes); } [TestMethod] public void FakesShouldNotBeGeneratedIfDisableAutoFakesSetToTrue() { - CommandLineOptions.Instance.DisableAutoFakes = true; + _commandLineOptions.DisableAutoFakes = true; string runSettingsXml = @".netstandard,Version=5.0"; - runSettingsXml = GenerateFakesUtilities.GenerateFakesSettings(CommandLineOptions.Instance, [], runSettingsXml); + runSettingsXml = GenerateFakesUtilities.GenerateFakesSettings(_commandLineOptions, [], runSettingsXml); Assert.AreEqual(runSettingsXml, _runSettings); } diff --git a/test/vstest.console.UnitTests/ExecutorUnitTests.cs b/test/vstest.console.UnitTests/ExecutorUnitTests.cs index 2f5c543c1b..9a598e869e 100644 --- a/test/vstest.console.UnitTests/ExecutorUnitTests.cs +++ b/test/vstest.console.UnitTests/ExecutorUnitTests.cs @@ -31,6 +31,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests; [DoNotParallelize] public class ExecutorUnitTests { + private readonly CommandLineOptions _commandLineOptions = new(); private readonly Mock _mockTestPlatformEventSource; public ExecutorUnitTests() @@ -389,7 +390,7 @@ public void MarkingTestRunFailedOnInjectedAggregatorIsObservedByExecutorExitCode new PlatformEnvironment(), RunSettingsManager.Instance, RunSettingsHelper.Instance, - CommandLineOptions.Instance, + _commandLineOptions, injectedAggregator).Execute("--help"); Assert.AreEqual(1, exitCodeWithInjected, "Executor must observe the injected aggregator's Failed outcome."); @@ -403,7 +404,7 @@ public void MarkingTestRunFailedOnInjectedAggregatorIsObservedByExecutorExitCode new PlatformEnvironment(), RunSettingsManager.Instance, RunSettingsHelper.Instance, - CommandLineOptions.Instance, + _commandLineOptions, TestRunResultAggregator.Instance).Execute("--help"); Assert.AreEqual(0, exitCodeWithStatic, "The static default aggregator is still Passed, so its Executor must not set the failure bit."); diff --git a/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs b/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs index 32a8d0535e..5996d2c138 100644 --- a/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs +++ b/test/vstest.console.UnitTests/Internal/ConsoleLoggerTests.cs @@ -1004,12 +1004,11 @@ public void TestRunCompleteHandlerShouldWriteToConsoleIfTestsAbortedWithoutRunni } [TestMethod] - public void TestRunStartHandlerShouldUseInjectedCommandLineOptionsSourcesRatherThanTheStaticDefault() + public void TestRunStartHandlerShouldUseInjectedCommandLineOptionsSourcesRatherThanASeparateInstance() { // The console logger reads the discovered sources from the CommandLineOptions it was given. Injecting a distinct - // instance (with its own source) while the static default stays empty proves the reader resolves to the injected - // instance rather than to CommandLineOptions.Instance. - CommandLineOptions.Reset(); + // instance (with its own source) while a separate instance stays empty proves the reader resolves to the injected + // instance rather than to some other CommandLineOptions. var fileHelper = new Mock(); var injectedOptions = new CommandLineOptions @@ -1021,8 +1020,8 @@ public void TestRunStartHandlerShouldUseInjectedCommandLineOptionsSourcesRatherT fileHelper.Setup(fh => fh.Exists(testFilePath)).Returns(true); injectedOptions.AddSource(testFilePath); - // The static default carries no sources, so a discovered count of 1 can only come from the injected instance. - Assert.AreEqual(0, CommandLineOptions.Instance.Sources.Count()); + // The separate instance carries no sources, so a discovered count of 1 can only come from the injected instance. + Assert.AreEqual(0, _commandLineOptions.Sources.Count()); var consoleLogger = new ConsoleLogger(_mockOutput.Object, _mockProgressIndicator.Object, _mockFeatureFlag.Object, injectedOptions); diff --git a/test/vstest.console.UnitTests/Processors/CLIRunSettingsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/CLIRunSettingsArgumentProcessorTests.cs index 75b72edad2..7eb2415847 100644 --- a/test/vstest.console.UnitTests/Processors/CLIRunSettingsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/CLIRunSettingsArgumentProcessorTests.cs @@ -57,7 +57,7 @@ public class CliRunSettingsArgumentProcessorTests public CliRunSettingsArgumentProcessorTests() { - _commandLineOptions = CommandLineOptions.Instance; + _commandLineOptions = new CommandLineOptions(); _settingsProvider = new TestableRunSettingsProvider(); _runSettingsHelper = new RunSettingsHelper(); _executor = new CliRunSettingsArgumentExecutor(_settingsProvider, _commandLineOptions, _runSettingsHelper); @@ -66,20 +66,19 @@ public CliRunSettingsArgumentProcessorTests() [TestCleanup] public void Cleanup() { - CommandLineOptions.Reset(); } [TestMethod] public void GetMetadataShouldReturnRunSettingsArgumentProcessorCapabilities() { - var processor = new CliRunSettingsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider(), _runSettingsHelper); + var processor = new CliRunSettingsArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider(), _runSettingsHelper); Assert.IsTrue(processor.Metadata.Value is CliRunSettingsArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnRunSettingsArgumentProcessorCapabilities() { - var processor = new CliRunSettingsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider(), _runSettingsHelper); + var processor = new CliRunSettingsArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider(), _runSettingsHelper); Assert.IsTrue(processor.Executor!.Value is CliRunSettingsArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/DisableAutoFakesArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/DisableAutoFakesArgumentProcessorTests.cs index d85307f85e..a41bd02c36 100644 --- a/test/vstest.console.UnitTests/Processors/DisableAutoFakesArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/DisableAutoFakesArgumentProcessorTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.VisualStudio.TestPlatform.CommandLine.Processors; @@ -10,11 +10,12 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] public class DisableAutoFakesArgumentProcessorTests { + private readonly CommandLineOptions _commandLineOptions = new(); private readonly DisableAutoFakesArgumentProcessor _disableAutoFakesArgumentProcessor; public DisableAutoFakesArgumentProcessorTests() { - _disableAutoFakesArgumentProcessor = new DisableAutoFakesArgumentProcessor(CommandLineOptions.Instance); + _disableAutoFakesArgumentProcessor = new DisableAutoFakesArgumentProcessor(_commandLineOptions); } [TestMethod] @@ -48,6 +49,6 @@ public void DisableAutoFakesArgumentProcessorExecutorShouldThrowIfArgumentIsNotB public void DisableAutoFakesArgumentProcessorExecutorShouldSetCommandLineDisableAutoFakeValueAsPerArgumentProvided() { _disableAutoFakesArgumentProcessor.Executor!.Value.Initialize("true"); - Assert.IsTrue(CommandLineOptions.Instance.DisableAutoFakes); + Assert.IsTrue(_commandLineOptions.DisableAutoFakes); } } diff --git a/test/vstest.console.UnitTests/Processors/EnableCodeCoverageArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/EnableCodeCoverageArgumentProcessorTests.cs index 09ef544abf..5958c736e2 100644 --- a/test/vstest.console.UnitTests/Processors/EnableCodeCoverageArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/EnableCodeCoverageArgumentProcessorTests.cs @@ -18,6 +18,7 @@ namespace vstest.console.UnitTests.Processors; [TestClass] public class EnableCodeCoverageArgumentProcessorTests { + private readonly CommandLineOptions _commandLineOptions = new(); private readonly TestableRunSettingsProvider _settingsProvider; private readonly EnableCodeCoverageArgumentExecutor _executor; @@ -32,7 +33,7 @@ public class EnableCodeCoverageArgumentProcessorTests public EnableCodeCoverageArgumentProcessorTests() { _settingsProvider = new TestableRunSettingsProvider(); - _executor = new EnableCodeCoverageArgumentExecutor(CommandLineOptions.Instance, _settingsProvider, + _executor = new EnableCodeCoverageArgumentExecutor(_commandLineOptions, _settingsProvider, new Mock().Object); CollectArgumentExecutor.EnabledDataCollectors.Clear(); } @@ -40,14 +41,14 @@ public EnableCodeCoverageArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnEnableCodeCoverageArgumentProcessorCapabilities() { - var processor = new EnableCodeCoverageArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); + var processor = new EnableCodeCoverageArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is EnableCodeCoverageArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnEnableCodeCoverageArgumentProcessorCapabilities() { - var processor = new EnableCodeCoverageArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); + var processor = new EnableCodeCoverageArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is EnableCodeCoverageArgumentExecutor); } @@ -79,11 +80,11 @@ public void InitializeShouldSetEnableCodeCoverageOfCommandLineOption() runsettings.LoadSettingsXml(runsettingsString); _settingsProvider.SetActiveRunSettings(runsettings); - CommandLineOptions.Instance.EnableCodeCoverage = false; + _commandLineOptions.EnableCodeCoverage = false; _executor.Initialize(string.Empty); - Assert.IsTrue(CommandLineOptions.Instance.EnableCodeCoverage, + Assert.IsTrue(_commandLineOptions.EnableCodeCoverage, "/EnableCoverage should set CommandLineOption.EnableCodeCoverage to true"); } diff --git a/test/vstest.console.UnitTests/Processors/EnvironmentArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/EnvironmentArgumentProcessorTests.cs index 4c827f9d77..73e98732b8 100644 --- a/test/vstest.console.UnitTests/Processors/EnvironmentArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/EnvironmentArgumentProcessorTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Globalization; @@ -31,7 +31,7 @@ public class EnvironmentArgumentProcessorTests public EnvironmentArgumentProcessorTests() { - _commandLineOptions = CommandLineOptions.Instance; + _commandLineOptions = new CommandLineOptions(); _settingsProvider = new TestableRunSettingsProvider(); _settingsProvider.UpdateRunSettings(DefaultRunSettings); _mockOutput = new Mock(); @@ -40,7 +40,6 @@ public EnvironmentArgumentProcessorTests() [TestCleanup] public void Cleanup() { - CommandLineOptions.Reset(); } [TestMethod] diff --git a/test/vstest.console.UnitTests/Processors/FrameworkArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/FrameworkArgumentProcessorTests.cs index ded2a9674d..e0461981c5 100644 --- a/test/vstest.console.UnitTests/Processors/FrameworkArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/FrameworkArgumentProcessorTests.cs @@ -15,31 +15,31 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] public class FrameworkArgumentProcessorTests { + private readonly CommandLineOptions _commandLineOptions = new(); private readonly FrameworkArgumentExecutor _executor; private readonly TestableRunSettingsProvider _runSettingsProvider; public FrameworkArgumentProcessorTests() { _runSettingsProvider = new TestableRunSettingsProvider(); - _executor = new FrameworkArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider); + _executor = new FrameworkArgumentExecutor(_commandLineOptions, _runSettingsProvider); } [TestCleanup] public void TestCleanup() { - CommandLineOptions.Reset(); } [TestMethod] public void GetMetadataShouldReturnFrameworkArgumentProcessorCapabilities() { - var processor = new FrameworkArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); + var processor = new FrameworkArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is FrameworkArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnFrameworkArgumentExecutor() { - var processor = new FrameworkArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); + var processor = new FrameworkArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is FrameworkArgumentExecutor); } @@ -91,7 +91,7 @@ public void InitializeShouldThrowIfArgumentIsInvalid() public void InitializeShouldSetCommandLineOptionsAndRunSettingsFramework() { _executor.Initialize(".NETCoreApp,Version=v1.0"); - Assert.AreEqual(".NETCoreApp,Version=v1.0", CommandLineOptions.Instance.TargetFrameworkVersion!.Name); + Assert.AreEqual(".NETCoreApp,Version=v1.0", _commandLineOptions.TargetFrameworkVersion!.Name); Assert.AreEqual(".NETCoreApp,Version=v1.0", _runSettingsProvider.QueryRunSettingsNode(FrameworkArgumentExecutor.RunSettingsPath)); } @@ -99,7 +99,7 @@ public void InitializeShouldSetCommandLineOptionsAndRunSettingsFramework() public void InitializeShouldSetCommandLineOptionsFrameworkForOlderFrameworks() { _executor.Initialize("Framework35"); - Assert.AreEqual(".NETFramework,Version=v3.5", CommandLineOptions.Instance.TargetFrameworkVersion!.Name); + Assert.AreEqual(".NETFramework,Version=v3.5", _commandLineOptions.TargetFrameworkVersion!.Name); Assert.AreEqual(".NETFramework,Version=v3.5", _runSettingsProvider.QueryRunSettingsNode(FrameworkArgumentExecutor.RunSettingsPath)); } @@ -107,7 +107,7 @@ public void InitializeShouldSetCommandLineOptionsFrameworkForOlderFrameworks() public void InitializeShouldSetCommandLineOptionsFrameworkForCaseInsensitiveFramework() { _executor.Initialize(".netcoreApp,Version=v1.0"); - Assert.AreEqual(".NETCoreApp,Version=v1.0", CommandLineOptions.Instance.TargetFrameworkVersion!.Name); + Assert.AreEqual(".NETCoreApp,Version=v1.0", _commandLineOptions.TargetFrameworkVersion!.Name); Assert.AreEqual(".NETCoreApp,Version=v1.0", _runSettingsProvider.QueryRunSettingsNode(FrameworkArgumentExecutor.RunSettingsPath)); } @@ -115,9 +115,9 @@ public void InitializeShouldSetCommandLineOptionsFrameworkForCaseInsensitiveFram public void InitializeShouldNotSetFrameworkIfSettingsFileIsLegacy() { _runSettingsProvider.UpdateRunSettingsNode(FrameworkArgumentExecutor.RunSettingsPath, nameof(FrameworkVersion.Framework45)); - CommandLineOptions.Instance.SettingsFile = @"c:\tmp\settings.testsettings"; + _commandLineOptions.SettingsFile = @"c:\tmp\settings.testsettings"; _executor.Initialize(".NETFramework,Version=v3.5"); - Assert.AreEqual(".NETFramework,Version=v3.5", CommandLineOptions.Instance.TargetFrameworkVersion!.Name); + Assert.AreEqual(".NETFramework,Version=v3.5", _commandLineOptions.TargetFrameworkVersion!.Name); Assert.AreEqual(nameof(FrameworkVersion.Framework45), _runSettingsProvider.QueryRunSettingsNode(FrameworkArgumentExecutor.RunSettingsPath)); } diff --git a/test/vstest.console.UnitTests/Processors/InIsolationArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/InIsolationArgumentProcessorTests.cs index b7393870ae..00a83559a4 100644 --- a/test/vstest.console.UnitTests/Processors/InIsolationArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/InIsolationArgumentProcessorTests.cs @@ -14,39 +14,39 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] public class InIsolationArgumentProcessorTests { + private readonly CommandLineOptions _commandLineOptions = new(); private readonly InIsolationArgumentExecutor _executor; private readonly TestableRunSettingsProvider _runSettingsProvider; public InIsolationArgumentProcessorTests() { _runSettingsProvider = new TestableRunSettingsProvider(); - _executor = new InIsolationArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider); + _executor = new InIsolationArgumentExecutor(_commandLineOptions, _runSettingsProvider); } [TestCleanup] public void TestCleanup() { - CommandLineOptions.Reset(); } [TestMethod] public void GetMetadataShouldReturnInProcessArgumentProcessorCapabilities() { - var processor = new InIsolationArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); + var processor = new InIsolationArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is InIsolationArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnInProcessArgumentExecutor() { - var processor = new InIsolationArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); + var processor = new InIsolationArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is InIsolationArgumentExecutor); } [TestMethod] public void InIsolationArgumentProcessorMetadataShouldProvideAppropriateCapabilities() { - var isolationProcessor = new InIsolationArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); + var isolationProcessor = new InIsolationArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider()); Assert.IsFalse(isolationProcessor.Metadata.Value.AllowMultiple); Assert.IsFalse(isolationProcessor.Metadata.Value.AlwaysExecute); Assert.IsFalse(isolationProcessor.Metadata.Value.IsAction); @@ -70,7 +70,7 @@ public void InIsolationArgumentProcessorExecutorShouldThrowIfArgumentIsProvided( public void InitializeShouldSetInIsolationValue() { _executor.Initialize(null); - Assert.IsTrue(CommandLineOptions.Instance.InIsolation, "InProcess option must be set to true."); + Assert.IsTrue(_commandLineOptions.InIsolation, "InProcess option must be set to true."); Assert.AreEqual("true", _runSettingsProvider.QueryRunSettingsNode(InIsolationArgumentExecutor.RunSettingsPath)); } diff --git a/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs index 7e2db70211..7d9a7840f7 100644 --- a/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs @@ -38,6 +38,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] public class ListFullyQualifiedTestsArgumentProcessorTests { + private readonly CommandLineOptions _commandLineOptions = new(); private readonly Mock _mockFileHelper; private readonly Mock _mockAssemblyMetadataProvider; private readonly InferHelper _inferHelper; @@ -51,13 +52,13 @@ public class ListFullyQualifiedTestsArgumentProcessorTests private readonly Mock _mockEnvironment; private readonly Mock _mockEnvironmentVariableHelper; - private static ListFullyQualifiedTestsArgumentExecutor GetExecutor(ITestRequestManager testRequestManager, IOutput? output) + private ListFullyQualifiedTestsArgumentExecutor GetExecutor(ITestRequestManager testRequestManager, IOutput? output) { var runSettingsProvider = new TestableRunSettingsProvider(); runSettingsProvider.AddDefaultRunSettings(); var listFullyQualifiedTestsArgumentExecutor = new ListFullyQualifiedTestsArgumentExecutor( - CommandLineOptions.Instance, + _commandLineOptions, runSettingsProvider, testRequestManager, output ?? ConsoleOutput.Instance); @@ -68,7 +69,6 @@ private static ListFullyQualifiedTestsArgumentExecutor GetExecutor(ITestRequestM public void Cleanup() { File.Delete(_dummyFilePath); - CommandLineOptions.Reset(); } public ListFullyQualifiedTestsArgumentProcessorTests() @@ -95,7 +95,7 @@ public ListFullyQualifiedTestsArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnListFullyQualifiedTestsArgumentProcessorCapabilities() { - var processor = new ListFullyQualifiedTestsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider(), new Mock().Object); + var processor = new ListFullyQualifiedTestsArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider(), new Mock().Object); Assert.IsTrue(processor.Metadata.Value is ListFullyQualifiedTestsArgumentProcessorCapabilities); } @@ -105,7 +105,7 @@ public void GetMetadataShouldReturnListFullyQualifiedTestsArgumentProcessorCapab [TestMethod] public void GetExecuterShouldReturnListFullyQualifiedTestsArgumentProcessorCapabilities() { - var processor = new ListFullyQualifiedTestsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider(), new Mock().Object); + var processor = new ListFullyQualifiedTestsArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider(), new Mock().Object); Assert.IsTrue(processor.Executor!.Value is ListFullyQualifiedTestsArgumentExecutor); } @@ -131,21 +131,20 @@ public void CapabilitiesShouldReturnAppropriateProperties() [TestMethod] public void ExecutorInitializeWithValidSourceShouldAddItToTestSources() { - CommandLineOptions.Instance.FileHelper = _mockFileHelper.Object; - CommandLineOptions.Instance.FilePatternParser = new FilePatternParser(new Mock().Object, _mockFileHelper.Object); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + _commandLineOptions.FileHelper = _mockFileHelper.Object; + _commandLineOptions.FilePatternParser = new FilePatternParser(new Mock().Object, _mockFileHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager, null); executor.Initialize(_dummyTestFilePath); - Assert.IsTrue(Enumerable.Contains(CommandLineOptions.Instance.Sources, _dummyTestFilePath)); + Assert.IsTrue(Enumerable.Contains(_commandLineOptions.Sources, _dummyTestFilePath)); } [TestMethod] public void ExecutorExecuteForNoSourcesShouldReturnFail() { - CommandLineOptions.Reset(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager, null); Assert.ThrowsExactly(() => executor.Execute()); @@ -162,7 +161,7 @@ public void ExecutorExecuteShouldThrowTestPlatformException() ResetAndAddSourceToCommandLineOptions(true); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager, null); @@ -179,7 +178,7 @@ public void ExecutorExecuteShouldThrowSettingsException() mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); ResetAndAddSourceToCommandLineOptions(true); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var listTestsArgumentExecutor = GetExecutor(testRequestManager, null); @@ -198,7 +197,7 @@ public void ExecutorExecuteShouldThrowInvalidOperationException() ResetAndAddSourceToCommandLineOptions(true); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var listTestsArgumentExecutor = GetExecutor(testRequestManager, null); @@ -216,7 +215,7 @@ public void ExecutorExecuteShouldThrowOtherExceptions() ResetAndAddSourceToCommandLineOptions(true); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager, null); @@ -304,7 +303,7 @@ private void RunListFullyQualifiedTestArgumentProcessorWithTraits(Mock tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); ResetAndAddSourceToCommandLineOptions(legitPath); - var cmdOptions = CommandLineOptions.Instance; + var cmdOptions = _commandLineOptions; cmdOptions.TestCaseFilterValue = "TestCategory=MyCat"; var testRequestManager = new TestRequestManager(cmdOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); @@ -325,18 +324,17 @@ private void RunListFullyQualifiedTestArgumentProcessorExecuteWithMockSetup(Mock ResetAndAddSourceToCommandLineOptions(legitPath); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); GetExecutor(testRequestManager, mockConsoleOutput.Object).Execute(); } private void ResetAndAddSourceToCommandLineOptions(bool legitPath) { - CommandLineOptions.Reset(); - CommandLineOptions.Instance.FileHelper = _mockFileHelper.Object; - CommandLineOptions.Instance.FilePatternParser = new FilePatternParser(new Mock().Object, _mockFileHelper.Object); - CommandLineOptions.Instance.AddSource(_dummyTestFilePath); - CommandLineOptions.Instance.ListTestsTargetPath = legitPath ? _dummyFilePath : string.Empty; + _commandLineOptions.FileHelper = _mockFileHelper.Object; + _commandLineOptions.FilePatternParser = new FilePatternParser(new Mock().Object, _mockFileHelper.Object); + _commandLineOptions.AddSource(_dummyTestFilePath); + _commandLineOptions.ListTestsTargetPath = legitPath ? _dummyFilePath : string.Empty; } } diff --git a/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs index a894fd7b66..c876105d61 100644 --- a/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ListTestsArgumentProcessorTests.cs @@ -37,6 +37,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] public class ListTestsArgumentProcessorTests { + private readonly CommandLineOptions _commandLineOptions = new(); private readonly Mock _mockFileHelper; private readonly Mock _mockAssemblyMetadataProvider; private readonly InferHelper _inferHelper; @@ -49,14 +50,14 @@ public class ListTestsArgumentProcessorTests private readonly Mock _mockEnvironment; private readonly Mock _mockEnvironmentVariableHelper; - private static ListTestsArgumentExecutor GetExecutor(ITestRequestManager testRequestManager, IOutput? output) + private ListTestsArgumentExecutor GetExecutor(ITestRequestManager testRequestManager, IOutput? output) { var runSettingsProvider = new TestableRunSettingsProvider(); runSettingsProvider.AddDefaultRunSettings(); var listTestsArgumentExecutor = new ListTestsArgumentExecutor( - CommandLineOptions.Instance, + _commandLineOptions, runSettingsProvider, testRequestManager, output ?? ConsoleOutput.Instance); @@ -66,7 +67,6 @@ private static ListTestsArgumentExecutor GetExecutor(ITestRequestManager testReq [TestCleanup] public void Cleanup() { - CommandLineOptions.Reset(); } public ListTestsArgumentProcessorTests() @@ -93,7 +93,7 @@ public ListTestsArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnListTestsArgumentProcessorCapabilities() { - var processor = new ListTestsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider(), new Mock().Object); + var processor = new ListTestsArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider(), new Mock().Object); Assert.IsTrue(processor.Metadata.Value is ListTestsArgumentProcessorCapabilities); } @@ -103,7 +103,7 @@ public void GetMetadataShouldReturnListTestsArgumentProcessorCapabilities() [TestMethod] public void GetExecuterShouldReturnListTestsArgumentProcessorCapabilities() { - var processor = new ListTestsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider(), new Mock().Object); + var processor = new ListTestsArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider(), new Mock().Object); Assert.IsTrue(processor.Executor!.Value is ListTestsArgumentExecutor); } @@ -133,22 +133,21 @@ public void CapabilitiesShouldReturnAppropriateProperties() [TestMethod] public void ExecutorInitializeWithValidSourceShouldAddItToTestSources() { - CommandLineOptions.Instance.FileHelper = _mockFileHelper.Object; - CommandLineOptions.Instance.FilePatternParser = new FilePatternParser(new Mock().Object, _mockFileHelper.Object); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + _commandLineOptions.FileHelper = _mockFileHelper.Object; + _commandLineOptions.FilePatternParser = new FilePatternParser(new Mock().Object, _mockFileHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager, null); executor.Initialize(_dummyTestFilePath); - Assert.IsTrue(Enumerable.Contains(CommandLineOptions.Instance.Sources, _dummyTestFilePath)); + Assert.IsTrue(Enumerable.Contains(_commandLineOptions.Sources, _dummyTestFilePath)); } [TestMethod] public void ExecutorExecuteForNoSourcesShouldReturnFail() { - CommandLineOptions.Reset(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager, null); Assert.ThrowsExactly(() => executor.Execute()); @@ -165,7 +164,7 @@ public void ExecutorExecuteShouldThrowTestPlatformException() ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager, null); Assert.ThrowsExactly(() => executor.Execute()); @@ -182,7 +181,7 @@ public void ExecutorExecuteShouldThrowSettingsException() ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var listTestsArgumentExecutor = GetExecutor(testRequestManager, null); Assert.ThrowsExactly(() => listTestsArgumentExecutor.Execute()); @@ -199,7 +198,7 @@ public void ExecutorExecuteShouldThrowInvalidOperationException() ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var listTestsArgumentExecutor = GetExecutor(testRequestManager, null); Assert.ThrowsExactly(() => listTestsArgumentExecutor.Execute()); @@ -216,7 +215,7 @@ public void ExecutorExecuteShouldThrowOtherExceptions() ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager, null); Assert.ThrowsExactly(() => executor.Execute()); @@ -275,16 +274,15 @@ private void RunListTestArgumentProcessorExecuteWithMockSetup(Mock().Object, _mockFileHelper.Object); - CommandLineOptions.Instance.AddSource(_dummyTestFilePath); + _commandLineOptions.FileHelper = _mockFileHelper.Object; + _commandLineOptions.FilePatternParser = new FilePatternParser(new Mock().Object, _mockFileHelper.Object); + _commandLineOptions.AddSource(_dummyTestFilePath); } } diff --git a/test/vstest.console.UnitTests/Processors/ListTestsTargetPathArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ListTestsTargetPathArgumentProcessorTests.cs index ce15ef079e..a664f697ae 100644 --- a/test/vstest.console.UnitTests/Processors/ListTestsTargetPathArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ListTestsTargetPathArgumentProcessorTests.cs @@ -11,17 +11,18 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] public class ListTestsTargetPathArgumentProcessorTests { + private readonly CommandLineOptions _commandLineOptions = new(); [TestMethod] public void GetMetadataShouldReturnListTestsTargetPathArgumentProcessorCapabilities() { - ListTestsTargetPathArgumentProcessor processor = new(CommandLineOptions.Instance); + ListTestsTargetPathArgumentProcessor processor = new(_commandLineOptions); Assert.IsTrue(processor.Metadata.Value is ListTestsTargetPathArgumentProcessorCapabilities); } [TestMethod] public void GetExecutorShouldReturnListTestsTargetPathArgumentProcessorCapabilities() { - ListTestsTargetPathArgumentProcessor processor = new(CommandLineOptions.Instance); + ListTestsTargetPathArgumentProcessor processor = new(_commandLineOptions); Assert.IsTrue(processor.Executor!.Value is ListTestsTargetPathArgumentExecutor); } @@ -46,7 +47,7 @@ public void CapabilitiesShouldAppropriateProperties() [TestMethod] public void ExecutorInitializeWithNullOrEmptyListTestsTargetPathShouldThrowCommandLineException() { - var options = CommandLineOptions.Instance; + var options = new CommandLineOptions(); ListTestsTargetPathArgumentExecutor executor = new(options); var ex = Assert.ThrowsExactly(() => executor.Initialize(null)); @@ -56,7 +57,7 @@ public void ExecutorInitializeWithNullOrEmptyListTestsTargetPathShouldThrowComma [TestMethod] public void ExecutorInitializeWithValidListTestsTargetPathShouldAddListTestsTargetPathToCommandLineOptions() { - var options = CommandLineOptions.Instance; + var options = new CommandLineOptions(); ListTestsTargetPathArgumentExecutor executor = new(options); executor.Initialize(@"C:\sample.txt"); @@ -66,7 +67,7 @@ public void ExecutorInitializeWithValidListTestsTargetPathShouldAddListTestsTarg [TestMethod] public void ExecutorListTestsTargetPathArgumentProcessorResultSuccess() { - var executor = new ListTestsTargetPathArgumentExecutor(CommandLineOptions.Instance); + var executor = new ListTestsTargetPathArgumentExecutor(_commandLineOptions); var result = executor.Execute(); Assert.AreEqual(ArgumentProcessorResult.Success, result); } diff --git a/test/vstest.console.UnitTests/Processors/ParallelArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ParallelArgumentProcessorTests.cs index e997e6c484..236cc2a3f0 100644 --- a/test/vstest.console.UnitTests/Processors/ParallelArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ParallelArgumentProcessorTests.cs @@ -12,31 +12,31 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] public class ParallelArgumentProcessorTests { + private readonly CommandLineOptions _commandLineOptions = new(); private readonly ParallelArgumentExecutor _executor; private readonly TestableRunSettingsProvider _runSettingsProvider; public ParallelArgumentProcessorTests() { _runSettingsProvider = new TestableRunSettingsProvider(); - _executor = new ParallelArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider); + _executor = new ParallelArgumentExecutor(_commandLineOptions, _runSettingsProvider); } [TestCleanup] public void TestCleanup() { - CommandLineOptions.Reset(); } [TestMethod] public void GetMetadataShouldReturnParallelArgumentProcessorCapabilities() { - var processor = new ParallelArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); + var processor = new ParallelArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is ParallelArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnParallelArgumentExecutor() { - var processor = new ParallelArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); + var processor = new ParallelArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is ParallelArgumentExecutor); } @@ -76,7 +76,7 @@ public void InitializeShouldThrowIfArgumentIsNonNull() public void InitializeShouldSetParallelValue() { _executor.Initialize(null); - Assert.IsTrue(CommandLineOptions.Instance.Parallel, "Parallel option must be set to true."); + Assert.IsTrue(_commandLineOptions.Parallel, "Parallel option must be set to true."); Assert.AreEqual("0", _runSettingsProvider.QueryRunSettingsNode(ParallelArgumentExecutor.RunSettingsPath)); } diff --git a/test/vstest.console.UnitTests/Processors/ParentProcessIdArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ParentProcessIdArgumentProcessorTests.cs index 7a2edfbf95..6eca68b152 100644 --- a/test/vstest.console.UnitTests/Processors/ParentProcessIdArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ParentProcessIdArgumentProcessorTests.cs @@ -12,17 +12,18 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] public class ParentProcessIdArgumentProcessorTests { + private readonly CommandLineOptions _commandLineOptions = new(); [TestMethod] public void GetMetadataShouldReturnParentProcessIdArgumentProcessorCapabilities() { - var processor = new ParentProcessIdArgumentProcessor(CommandLineOptions.Instance); + var processor = new ParentProcessIdArgumentProcessor(_commandLineOptions); Assert.IsTrue(processor.Metadata.Value is ParentProcessIdArgumentProcessorCapabilities); } [TestMethod] public void GetExecutorShouldReturnParentProcessIdArgumentProcessorCapabilities() { - var processor = new ParentProcessIdArgumentProcessor(CommandLineOptions.Instance); + var processor = new ParentProcessIdArgumentProcessor(_commandLineOptions); Assert.IsTrue(processor.Executor!.Value is ParentProcessIdArgumentExecutor); } @@ -55,7 +56,7 @@ public void CapabilitiesShouldReturnAppropriateProperties() [TestMethod] public void ExecutorInitializeWithNullOrEmptyParentProcessIdShouldThrowCommandLineException() { - var executor = new ParentProcessIdArgumentExecutor(CommandLineOptions.Instance); + var executor = new ParentProcessIdArgumentExecutor(_commandLineOptions); var ex = Assert.ThrowsExactly(() => executor.Initialize(null)); Assert.AreEqual("The --ParentProcessId|/ParentProcessId argument requires the process id which is an integer. Specify the process id of the parent process that launched this process.", ex.Message); } @@ -63,7 +64,7 @@ public void ExecutorInitializeWithNullOrEmptyParentProcessIdShouldThrowCommandLi [TestMethod] public void ExecutorInitializeWithInvalidParentProcessIdShouldThrowCommandLineException() { - var executor = new ParentProcessIdArgumentExecutor(CommandLineOptions.Instance); + var executor = new ParentProcessIdArgumentExecutor(_commandLineOptions); var ex = Assert.ThrowsExactly(() => executor.Initialize("Foo")); Assert.AreEqual("The --ParentProcessId|/ParentProcessId argument requires the process id which is an integer. Specify the process id of the parent process that launched this process.", ex.Message); } @@ -71,16 +72,16 @@ public void ExecutorInitializeWithInvalidParentProcessIdShouldThrowCommandLineEx [TestMethod] public void ExecutorInitializeWithValidPortShouldAddParentProcessIdToCommandLineOptions() { - var executor = new ParentProcessIdArgumentExecutor(CommandLineOptions.Instance); + var executor = new ParentProcessIdArgumentExecutor(_commandLineOptions); int parentProcessId = 2345; executor.Initialize(parentProcessId.ToString(CultureInfo.InvariantCulture)); - Assert.AreEqual(parentProcessId, CommandLineOptions.Instance.ParentProcessId); + Assert.AreEqual(parentProcessId, _commandLineOptions.ParentProcessId); } [TestMethod] public void ExecutorExecuteReturnsArgumentProcessorResultSuccess() { - var executor = new ParentProcessIdArgumentExecutor(CommandLineOptions.Instance); + var executor = new ParentProcessIdArgumentExecutor(_commandLineOptions); int parentProcessId = 2345; executor.Initialize(parentProcessId.ToString(CultureInfo.InvariantCulture)); diff --git a/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs index cb4dfbbbc8..88aad7de9a 100644 --- a/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/PlatformArgumentProcessorTests.cs @@ -16,6 +16,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] public class PlatformArgumentProcessorTests { + private readonly CommandLineOptions _commandLineOptions = new(); private readonly PlatformArgumentExecutor _executor; private readonly TestableRunSettingsProvider _runSettingsProvider; private readonly IRunSettingsHelper _runSettingsHelper; @@ -24,26 +25,25 @@ public PlatformArgumentProcessorTests() { _runSettingsProvider = new TestableRunSettingsProvider(); _runSettingsHelper = new RunSettingsHelper(); - _executor = new PlatformArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider, _runSettingsHelper); + _executor = new PlatformArgumentExecutor(_commandLineOptions, _runSettingsProvider, _runSettingsHelper); } [TestCleanup] public void TestCleanup() { - CommandLineOptions.Reset(); } [TestMethod] public void GetMetadataShouldReturnPlatformArgumentProcessorCapabilities() { - var processor = new PlatformArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider(), _runSettingsHelper); + var processor = new PlatformArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider(), _runSettingsHelper); Assert.IsTrue(processor.Metadata.Value is PlatformArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnPlatformArgumentExecutor() { - var processor = new PlatformArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider(), _runSettingsHelper); + var processor = new PlatformArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider(), _runSettingsHelper); Assert.IsTrue(processor.Executor!.Value is PlatformArgumentExecutor); } @@ -102,7 +102,7 @@ public void InitializeShouldThrowIfArgumentIsNotASupportedArchitecture() public void InitializeShouldSetCommandLineOptionsArchitecture() { _executor.Initialize("x64"); - Assert.AreEqual(ObjectModel.Architecture.X64, CommandLineOptions.Instance.TargetArchitecture); + Assert.AreEqual(ObjectModel.Architecture.X64, _commandLineOptions.TargetArchitecture); Assert.AreEqual(nameof(ObjectModel.Architecture.X64), _runSettingsProvider.QueryRunSettingsNode(PlatformArgumentExecutor.RunSettingsPath)); } @@ -110,7 +110,7 @@ public void InitializeShouldSetCommandLineOptionsArchitecture() public void InitializeShouldNotConsiderCaseSensitivityOfTheArgumentPassed() { _executor.Initialize("ArM"); - Assert.AreEqual(ObjectModel.Architecture.ARM, CommandLineOptions.Instance.TargetArchitecture); + Assert.AreEqual(ObjectModel.Architecture.ARM, _commandLineOptions.TargetArchitecture); Assert.AreEqual(nameof(ObjectModel.Architecture.ARM), _runSettingsProvider.QueryRunSettingsNode(PlatformArgumentExecutor.RunSettingsPath)); } diff --git a/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs index 836582b19e..3b28beed53 100644 --- a/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/PortArgumentProcessorTests.cs @@ -22,6 +22,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] public class PortArgumentProcessorTests { + private readonly CommandLineOptions _commandLineOptions = new(); private readonly Mock _mockProcessHelper; private readonly Mock _testDesignModeClient; private readonly Mock _testRequestManager; @@ -34,20 +35,20 @@ public PortArgumentProcessorTests() _testDesignModeClient = new Mock(); _testRequestManager = new Mock(); _runSettingsHelper = new RunSettingsHelper(); - _executor = new PortArgumentExecutor(CommandLineOptions.Instance, _testRequestManager.Object, _runSettingsHelper); + _executor = new PortArgumentExecutor(_commandLineOptions, _testRequestManager.Object, _runSettingsHelper); } [TestMethod] public void GetMetadataShouldReturnPortArgumentProcessorCapabilities() { - var processor = new PortArgumentProcessor(CommandLineOptions.Instance, _runSettingsHelper, _testRequestManager.Object); + var processor = new PortArgumentProcessor(_commandLineOptions, _runSettingsHelper, _testRequestManager.Object); Assert.IsTrue(processor.Metadata.Value is PortArgumentProcessorCapabilities); } [TestMethod] public void GetExecutorShouldReturnPortArgumentProcessorCapabilities() { - var processor = new PortArgumentProcessor(CommandLineOptions.Instance, _runSettingsHelper, _testRequestManager.Object); + var processor = new PortArgumentProcessor(_commandLineOptions, _runSettingsHelper, _testRequestManager.Object); Assert.IsTrue(processor.Executor!.Value is PortArgumentExecutor); } @@ -89,11 +90,11 @@ public void ExecutorInitializeWithInvalidPortShouldThrowCommandLineException() public void ExecutorInitializeWithValidPortShouldAddPortToCommandLineOptionsAndInitializeDesignModeManager() { int port = 2345; - CommandLineOptions.Instance.ParentProcessId = 0; + _commandLineOptions.ParentProcessId = 0; _executor.Initialize(port.ToString(CultureInfo.InvariantCulture)); - Assert.AreEqual(port, CommandLineOptions.Instance.Port); + Assert.AreEqual(port, _commandLineOptions.Port); Assert.IsNotNull(DesignModeClient.Instance); } @@ -101,18 +102,18 @@ public void ExecutorInitializeWithValidPortShouldAddPortToCommandLineOptionsAndI public void ExecutorInitializeShouldSetDesignMode() { int port = 2345; - CommandLineOptions.Instance.ParentProcessId = 0; + _commandLineOptions.ParentProcessId = 0; _executor.Initialize(port.ToString(CultureInfo.InvariantCulture)); - Assert.IsTrue(CommandLineOptions.Instance.IsDesignMode); + Assert.IsTrue(_commandLineOptions.IsDesignMode); Assert.IsTrue(_runSettingsHelper.IsDesignMode); } [TestMethod] public void ExecutorInitializeShouldSetProcessExitCallback() { - _executor = new PortArgumentExecutor(CommandLineOptions.Instance, _testRequestManager.Object, _mockProcessHelper.Object, _runSettingsHelper); + _executor = new PortArgumentExecutor(_commandLineOptions, _testRequestManager.Object, _mockProcessHelper.Object, _runSettingsHelper); int port = 2345; #if NET5_0_OR_GREATER var pid = Environment.ProcessId; @@ -121,7 +122,7 @@ public void ExecutorInitializeShouldSetProcessExitCallback() using (var p = Process.GetCurrentProcess()) pid = p.Id; #endif - CommandLineOptions.Instance.ParentProcessId = pid; + _commandLineOptions.ParentProcessId = pid; _executor.Initialize(port.ToString(CultureInfo.InvariantCulture)); @@ -131,7 +132,7 @@ public void ExecutorInitializeShouldSetProcessExitCallback() [TestMethod] public void ExecutorExecuteForValidConnectionReturnsArgumentProcessorResultSuccess() { - _executor = new PortArgumentExecutor(CommandLineOptions.Instance, _testRequestManager.Object, + _executor = new PortArgumentExecutor(_commandLineOptions, _testRequestManager.Object, (parentProcessId, ph) => _testDesignModeClient.Object, _mockProcessHelper.Object, _runSettingsHelper); int port = 2345; @@ -147,7 +148,7 @@ public void ExecutorExecuteForValidConnectionReturnsArgumentProcessorResultSucce [TestMethod] public void ExecutorExecuteForFailedConnectionShouldThrowCommandLineException() { - _executor = new PortArgumentExecutor(CommandLineOptions.Instance, _testRequestManager.Object, + _executor = new PortArgumentExecutor(_commandLineOptions, _testRequestManager.Object, (parentProcessId, ph) => _testDesignModeClient.Object, _mockProcessHelper.Object, _runSettingsHelper); _testDesignModeClient.Setup(td => td.ConnectToClientAndProcessRequests(It.IsAny(), @@ -165,11 +166,11 @@ public void ExecutorExecuteForFailedConnectionShouldThrowCommandLineException() public void ExecutorExecuteSetsParentProcessIdOnDesignModeInitializer() { var parentProcessId = 2346; - var parentProcessIdArgumentExecutor = new ParentProcessIdArgumentExecutor(CommandLineOptions.Instance); + var parentProcessIdArgumentExecutor = new ParentProcessIdArgumentExecutor(_commandLineOptions); parentProcessIdArgumentExecutor.Initialize(parentProcessId.ToString(CultureInfo.InvariantCulture)); int actualParentProcessId = -1; - _executor = new PortArgumentExecutor(CommandLineOptions.Instance, + _executor = new PortArgumentExecutor(_commandLineOptions, _testRequestManager.Object, (ppid, ph) => { diff --git a/test/vstest.console.UnitTests/Processors/ResponseFileArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ResponseFileArgumentProcessorTests.cs index 7f948c8aab..c7317e0c83 100644 --- a/test/vstest.console.UnitTests/Processors/ResponseFileArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ResponseFileArgumentProcessorTests.cs @@ -9,12 +9,6 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] public class ResponseFileArgumentProcessorTests { - [TestCleanup] - public void TestCleanup() - { - CommandLineOptions.Reset(); - } - [TestMethod] public void GetMetadataShouldReturnResponseFileArgumentProcessorCapabilities() { diff --git a/test/vstest.console.UnitTests/Processors/ResultsDirectoryArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ResultsDirectoryArgumentProcessorTests.cs index a199088a72..c2943f7bcc 100644 --- a/test/vstest.console.UnitTests/Processors/ResultsDirectoryArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ResultsDirectoryArgumentProcessorTests.cs @@ -16,32 +16,32 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] public class ResultsDirectoryArgumentProcessorTests { + private readonly CommandLineOptions _commandLineOptions = new(); private readonly ResultsDirectoryArgumentExecutor _executor; private readonly TestableRunSettingsProvider _runSettingsProvider; public ResultsDirectoryArgumentProcessorTests() { _runSettingsProvider = new TestableRunSettingsProvider(); - _executor = new ResultsDirectoryArgumentExecutor(CommandLineOptions.Instance, _runSettingsProvider); + _executor = new ResultsDirectoryArgumentExecutor(_commandLineOptions, _runSettingsProvider); } [TestCleanup] public void TestCleanup() { - CommandLineOptions.Reset(); } [TestMethod] public void GetMetadataShouldReturnResultsDirectoryArgumentProcessorCapabilities() { - var processor = new ResultsDirectoryArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); + var processor = new ResultsDirectoryArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is ResultsDirectoryArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnResultsDirectoryArgumentExecutor() { - var processor = new ResultsDirectoryArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); + var processor = new ResultsDirectoryArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is ResultsDirectoryArgumentExecutor); } @@ -118,7 +118,7 @@ public void InitializeShouldSetCommandLineOptionsAndRunSettingsForRelativePathVa var relativePath = TranslatePath(@".\relative\path"); var absolutePath = Path.GetFullPath(relativePath); _executor.Initialize(relativePath); - Assert.AreEqual(absolutePath, CommandLineOptions.Instance.ResultsDirectory); + Assert.AreEqual(absolutePath, _commandLineOptions.ResultsDirectory); Assert.AreEqual(absolutePath, _runSettingsProvider.QueryRunSettingsNode(ResultsDirectoryArgumentExecutor.RunSettingsPath)); } @@ -127,7 +127,7 @@ public void InitializeShouldSetCommandLineOptionsAndRunSettingsForAbsolutePathVa { var absolutePath = TranslatePath(@"c:\random\someone\testresults"); _executor.Initialize(absolutePath); - Assert.AreEqual(absolutePath, CommandLineOptions.Instance.ResultsDirectory); + Assert.AreEqual(absolutePath, _commandLineOptions.ResultsDirectory); Assert.AreEqual(absolutePath, _runSettingsProvider.QueryRunSettingsNode(ResultsDirectoryArgumentExecutor.RunSettingsPath)); } diff --git a/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessorTests.cs index 6c752b55ff..62c8874665 100644 --- a/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/RunSettingsArgumentProcessorTests.cs @@ -25,6 +25,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] public class RunSettingsArgumentProcessorTests { + private readonly CommandLineOptions _commandLineOptions = new(); private readonly TestableRunSettingsProvider _settingsProvider; public RunSettingsArgumentProcessorTests() @@ -35,20 +36,19 @@ public RunSettingsArgumentProcessorTests() [TestCleanup] public void TestCleanup() { - CommandLineOptions.Reset(); } [TestMethod] public void GetMetadataShouldReturnRunSettingsArgumentProcessorCapabilities() { - var processor = new RunSettingsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider(), new RunSettingsHelper()); + var processor = new RunSettingsArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider(), new RunSettingsHelper()); Assert.IsTrue(processor.Metadata.Value is RunSettingsArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnRunSettingsArgumentExecutor() { - var processor = new RunSettingsArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider(), new RunSettingsHelper()); + var processor = new RunSettingsArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider(), new RunSettingsHelper()); Assert.IsTrue(processor.Executor!.Value is RunSettingsArgumentExecutor); } @@ -78,14 +78,14 @@ public void CapabilitiesShouldReturnAppropriateProperties() [TestMethod] public void InitializeShouldThrowExceptionIfArgumentIsNull() { - var ex = Assert.ThrowsExactly(() => new RunSettingsArgumentExecutor(CommandLineOptions.Instance, null!, new RunSettingsHelper()).Initialize(null)); + var ex = Assert.ThrowsExactly(() => new RunSettingsArgumentExecutor(_commandLineOptions, null!, new RunSettingsHelper()).Initialize(null)); Assert.Contains("The /Settings parameter requires a settings file to be provided.", ex.Message); } [TestMethod] public void InitializeShouldThrowExceptionIfArgumentIsWhiteSpace() { - var ex = Assert.ThrowsExactly(() => new RunSettingsArgumentExecutor(CommandLineOptions.Instance, null!, new RunSettingsHelper()).Initialize(" ")); + var ex = Assert.ThrowsExactly(() => new RunSettingsArgumentExecutor(_commandLineOptions, null!, new RunSettingsHelper()).Initialize(" ")); Assert.Contains("The /Settings parameter requires a settings file to be provided.", ex.Message); } @@ -94,7 +94,7 @@ public void InitializeShouldThrowExceptionIfFileDoesNotExist() { var fileName = "C:\\Imaginary\\nonExistentFile.txt"; - var executor = new RunSettingsArgumentExecutor(CommandLineOptions.Instance, null!, new RunSettingsHelper()); + var executor = new RunSettingsArgumentExecutor(_commandLineOptions, null!, new RunSettingsHelper()); var mockFileHelper = new Mock(); mockFileHelper.Setup(fh => fh.Exists(It.IsAny())).Returns(false); @@ -112,7 +112,7 @@ public void InitializeShouldThrowIfRunSettingsSchemaDoesNotMatch() var settingsXml = ""; var executor = new TestableRunSettingsArgumentExecutor( - CommandLineOptions.Instance, + _commandLineOptions, _settingsProvider, settingsXml); @@ -135,7 +135,7 @@ public void InitializeShouldSetActiveRunSettings() var settingsXml = ""; var executor = new TestableRunSettingsArgumentExecutor( - CommandLineOptions.Instance, + _commandLineOptions, _settingsProvider, settingsXml); @@ -149,7 +149,7 @@ public void InitializeShouldSetActiveRunSettings() // Assert. Assert.IsNotNull(_settingsProvider.ActiveRunSettings); - Assert.AreEqual(fileName, CommandLineOptions.Instance.SettingsFile); + Assert.AreEqual(fileName, _commandLineOptions.SettingsFile); } [TestMethod] @@ -160,7 +160,7 @@ public void InitializeShouldSetSettingsFileForCommandLineOptions() var settingsXml = ""; var executor = new TestableRunSettingsArgumentExecutor( - CommandLineOptions.Instance, + _commandLineOptions, _settingsProvider, settingsXml); @@ -173,7 +173,7 @@ public void InitializeShouldSetSettingsFileForCommandLineOptions() executor.Initialize(fileName); // Assert. - Assert.AreEqual(fileName, CommandLineOptions.Instance.SettingsFile); + Assert.AreEqual(fileName, _commandLineOptions.SettingsFile); } [TestMethod] @@ -184,7 +184,7 @@ public void InitializeShouldAddDefaultSettingsIfNotPresent() var settingsXml = ""; var executor = new TestableRunSettingsArgumentExecutor( - CommandLineOptions.Instance, + _commandLineOptions, _settingsProvider, settingsXml); @@ -214,7 +214,7 @@ public void InitializeShouldSetActiveRunSettingsForTestSettingsFiles() var settingsXml = ""; var executor = new TestableRunSettingsArgumentExecutor( - CommandLineOptions.Instance, + _commandLineOptions, _settingsProvider, settingsXml); @@ -258,7 +258,7 @@ public void InitializeShouldUpdateCommandLineOptionsArchitectureAndFxIfProvided( var settingsXml = $"{nameof(Architecture.X64)}{Constants.DotNetFramework46}"; var executor = new TestableRunSettingsArgumentExecutor( - CommandLineOptions.Instance, + _commandLineOptions, _settingsProvider, settingsXml); @@ -271,10 +271,10 @@ public void InitializeShouldUpdateCommandLineOptionsArchitectureAndFxIfProvided( executor.Initialize(fileName); // Assert. - Assert.IsTrue(CommandLineOptions.Instance.ArchitectureSpecified); - Assert.IsTrue(CommandLineOptions.Instance.FrameworkVersionSpecified); - Assert.AreEqual(Architecture.X64, CommandLineOptions.Instance.TargetArchitecture); - Assert.AreEqual(Constants.DotNetFramework46, CommandLineOptions.Instance.TargetFrameworkVersion.Name); + Assert.IsTrue(_commandLineOptions.ArchitectureSpecified); + Assert.IsTrue(_commandLineOptions.FrameworkVersionSpecified); + Assert.AreEqual(Architecture.X64, _commandLineOptions.TargetArchitecture); + Assert.AreEqual(Constants.DotNetFramework46, _commandLineOptions.TargetFrameworkVersion.Name); } [TestMethod] @@ -285,7 +285,7 @@ public void InitializeShouldNotUpdateCommandLineOptionsArchitectureAndFxIfNotPro var settingsXml = ""; var executor = new TestableRunSettingsArgumentExecutor( - CommandLineOptions.Instance, + _commandLineOptions, _settingsProvider, settingsXml); @@ -298,8 +298,8 @@ public void InitializeShouldNotUpdateCommandLineOptionsArchitectureAndFxIfNotPro executor.Initialize(fileName); // Assert. - Assert.IsFalse(CommandLineOptions.Instance.ArchitectureSpecified); - Assert.IsFalse(CommandLineOptions.Instance.FrameworkVersionSpecified); + Assert.IsFalse(_commandLineOptions.ArchitectureSpecified); + Assert.IsFalse(_commandLineOptions.FrameworkVersionSpecified); } [TestMethod] @@ -311,7 +311,7 @@ public void InitializeShouldPreserveActualJapaneseString() File.WriteAllText(runsettingsFile, settingsXml, Encoding.UTF8); var executor = new TestableRunSettingsArgumentExecutor( - CommandLineOptions.Instance, + _commandLineOptions, _settingsProvider, null); @@ -329,7 +329,7 @@ public void InitializeShouldSetInIsolationToTrueIfEnvironmentVariablesSpecified( var fileName = "C:\\temp\\r.runsettings"; var executor = new TestableRunSettingsArgumentExecutor( - CommandLineOptions.Instance, + _commandLineOptions, _settingsProvider, settingsXml); @@ -342,7 +342,7 @@ public void InitializeShouldSetInIsolationToTrueIfEnvironmentVariablesSpecified( executor.Initialize(fileName); // Assert. - Assert.IsTrue(CommandLineOptions.Instance.InIsolation); + Assert.IsTrue(_commandLineOptions.InIsolation); Assert.AreEqual("true", _settingsProvider.QueryRunSettingsNode(InIsolationArgumentExecutor.RunSettingsPath)); } @@ -355,7 +355,7 @@ public void InitializeShouldNotSetInIsolationToTrueIfEnvironmentVariablesNotSpec var fileName = "C:\\temp\\r.runsettings"; var executor = new TestableRunSettingsArgumentExecutor( - CommandLineOptions.Instance, + _commandLineOptions, _settingsProvider, settingsXml); @@ -368,7 +368,7 @@ public void InitializeShouldNotSetInIsolationToTrueIfEnvironmentVariablesNotSpec executor.Initialize(fileName); // Assert. - Assert.IsFalse(CommandLineOptions.Instance.InIsolation); + Assert.IsFalse(_commandLineOptions.InIsolation); Assert.IsNull(_settingsProvider.QueryRunSettingsNode(InIsolationArgumentExecutor.RunSettingsPath)); } @@ -381,7 +381,7 @@ public void InitializeShouldUpdateTestCaseFilterIfProvided() var settingsXml = $"{filter}"; var executor = new TestableRunSettingsArgumentExecutor( - CommandLineOptions.Instance, + _commandLineOptions, _settingsProvider, settingsXml); @@ -394,7 +394,7 @@ public void InitializeShouldUpdateTestCaseFilterIfProvided() executor.Initialize(fileName); // Assert. - Assert.AreEqual(filter, CommandLineOptions.Instance.TestCaseFilterValue); + Assert.AreEqual(filter, _commandLineOptions.TestCaseFilterValue); } #endregion diff --git a/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs index ad671bb77f..8c608fe62e 100644 --- a/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/RunSpecificTestsArgumentProcessorTests.cs @@ -33,6 +33,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] public class RunSpecificTestsArgumentProcessorTests { + private readonly CommandLineOptions _commandLineOptions = new(); private const string NoDiscoveredTestsWarning = @"No test is available in DummyTest.dll. Make sure that installed test discoverers & executors, platform & framework version settings are appropriate and try again."; private const string TestAdapterPathSuggestion = @"Additionally, path to test adapters can be specified using /TestAdapterPath command. Example /TestAdapterPath:."; private readonly Mock _mockFileHelper; @@ -53,7 +54,7 @@ private RunSpecificTestsArgumentExecutor GetExecutor(ITestRequestManager testReq { var runSettingsProvider = new TestableRunSettingsProvider(); runSettingsProvider.AddDefaultRunSettings(); - return new RunSpecificTestsArgumentExecutor(CommandLineOptions.Instance, runSettingsProvider, testRequestManager, _mockArtifactProcessingManager.Object, _mockOutput.Object); + return new RunSpecificTestsArgumentExecutor(_commandLineOptions, runSettingsProvider, testRequestManager, _mockArtifactProcessingManager.Object, _mockOutput.Object); } public RunSpecificTestsArgumentProcessorTests() @@ -81,7 +82,7 @@ public RunSpecificTestsArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnRunSpecificTestsArgumentProcessorCapabilities() { - RunSpecificTestsArgumentProcessor processor = new(CommandLineOptions.Instance, new TestableRunSettingsProvider(), new Mock().Object); + RunSpecificTestsArgumentProcessor processor = new(_commandLineOptions, new TestableRunSettingsProvider(), new Mock().Object); Assert.IsTrue(processor.Metadata.Value is RunSpecificTestsArgumentProcessorCapabilities); } @@ -89,7 +90,7 @@ public void GetMetadataShouldReturnRunSpecificTestsArgumentProcessorCapabilities [TestMethod] public void GetExecutorShouldReturnRunSpecificTestsArgumentExecutor() { - RunSpecificTestsArgumentProcessor processor = new(CommandLineOptions.Instance, new TestableRunSettingsProvider(), new Mock().Object); + RunSpecificTestsArgumentProcessor processor = new(_commandLineOptions, new TestableRunSettingsProvider(), new Mock().Object); Assert.IsTrue(processor.Executor!.Value is RunSpecificTestsArgumentExecutor); } @@ -119,9 +120,8 @@ public void CapabilitiesShouldReturnAppropriateProperties() [TestMethod] public void InitializeShouldThrowIfArgumentIsNull() { - CommandLineOptions.Reset(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Initialize(null)); @@ -130,9 +130,8 @@ public void InitializeShouldThrowIfArgumentIsNull() [TestMethod] public void InitializeShouldThrowIfArgumentIsEmpty() { - CommandLineOptions.Reset(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Initialize(string.Empty)); @@ -141,9 +140,8 @@ public void InitializeShouldThrowIfArgumentIsEmpty() [TestMethod] public void InitializeShouldThrowIfArgumentIsWhiteSpace() { - CommandLineOptions.Reset(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Initialize(" ")); @@ -152,9 +150,8 @@ public void InitializeShouldThrowIfArgumentIsWhiteSpace() [TestMethod] public void InitializeShouldThrowIfArgumentsAreEmpty() { - CommandLineOptions.Reset(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Initialize(" , ")); @@ -163,9 +160,8 @@ public void InitializeShouldThrowIfArgumentsAreEmpty() [TestMethod] public void ExecutorShouldSplitTestsSeparatedByComma() { - CommandLineOptions.Reset(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Execute()); @@ -174,9 +170,8 @@ public void ExecutorShouldSplitTestsSeparatedByComma() [TestMethod] public void ExecutorExecuteForNoSourcesShouldThrowCommandLineException() { - CommandLineOptions.Reset(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Execute()); @@ -201,10 +196,10 @@ public void ExecutorExecuteForValidSourceWithTestCaseFilterShouldRunTests() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockTestRunRequest.Object); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); - CommandLineOptions.Instance.TestCaseFilterValue = "Filter"; + _commandLineOptions.TestCaseFilterValue = "Filter"; executor.Initialize("Test1"); ArgumentProcessorResult argumentProcessorResult = executor.Execute(); @@ -226,7 +221,7 @@ public void ExecutorExecuteShouldThrowTestPlatformExceptionThrownDuringDiscovery mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Execute()); @@ -244,7 +239,7 @@ public void ExecutorExecuteShouldThrowInvalidOperationExceptionThrownDuringDisco mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Execute()); @@ -262,7 +257,7 @@ public void ExecutorExecuteShouldThrowSettingsExceptionThrownDuringDiscovery() mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Execute()); @@ -287,7 +282,7 @@ public void ExecutorExecuteShouldThrowTestPlatformExceptionThrownDuringExecution mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -314,7 +309,7 @@ public void ExecutorExecuteShouldThrowSettingsExceptionThrownDuringExecution() mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -342,7 +337,7 @@ public void ExecutorExecuteShouldThrowInvalidOperationExceptionThrownDuringExecu ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -359,11 +354,11 @@ public void ExecutorExecuteShouldForValidSourcesAndNoTestsDiscoveredShouldLogWar ResetAndAddSourceToCommandLineOptions(); // Setting some test adapter path - CommandLineOptions.Instance.TestAdapterPath = [@"C:\Foo"]; + _commandLineOptions.TestAdapterPath = [@"C:\Foo"]; mockDiscoveryRequest.Setup(dr => dr.DiscoverAsync()).Raises(dr => dr.OnDiscoveredTests += null, new DiscoveredTestsEventArgs(new List())); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -384,7 +379,7 @@ public void ExecutorExecuteShouldForValidSourcesAndNoTestsDiscoveredShouldLogApp mockDiscoveryRequest.Setup(dr => dr.DiscoverAsync()).Raises(dr => dr.OnDiscoveredTests += null, new DiscoveredTestsEventArgs(new List())); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -410,7 +405,7 @@ public void ExecutorExecuteShouldForValidSourcesAndValidSelectedTestsRunsTestsAn mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockTestRunRequest.Object); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -438,7 +433,7 @@ public void ExecutorShouldRunTestsWhenTestsAreCommaSeparated() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockTestRunRequest.Object); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1, Test2"); @@ -467,7 +462,7 @@ public void ExecutorShouldRunTestsWhenTestsAreFiltered() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockTestRunRequest.Object); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -492,7 +487,7 @@ public void ExecutorShouldWarnWhenTestsAreNotAvailable() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockTestRunRequest.Object); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1, Test2"); @@ -521,7 +516,7 @@ public void ExecutorShouldRunTestsWhenTestsAreCommaSeparatedWithEscape() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockTestRunRequest.Object); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1(a\\,b), Test2(c\\,d)"); @@ -553,7 +548,7 @@ public void ExecutorShouldDisplayWarningIfNoTestsAreExecuted() mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -584,7 +579,7 @@ public void ExecutorShouldNotDisplayWarningIfTestsAreExecuted() mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -597,10 +592,9 @@ public void ExecutorShouldNotDisplayWarningIfTestsAreExecuted() private void ResetAndAddSourceToCommandLineOptions() { - CommandLineOptions.Reset(); - CommandLineOptions.Instance.TestCaseFilterValue = null; - CommandLineOptions.Instance.FilePatternParser = new FilePatternParser(new Mock().Object, _mockFileHelper.Object); - CommandLineOptions.Instance.FileHelper = _mockFileHelper.Object; - CommandLineOptions.Instance.AddSource(_dummyTestFilePath); + _commandLineOptions.TestCaseFilterValue = null; + _commandLineOptions.FilePatternParser = new FilePatternParser(new Mock().Object, _mockFileHelper.Object); + _commandLineOptions.FileHelper = _mockFileHelper.Object; + _commandLineOptions.AddSource(_dummyTestFilePath); } } diff --git a/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs index c91e6c7849..ca6dacfcd7 100644 --- a/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs @@ -40,6 +40,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] public class RunTestsArgumentProcessorTests { + private readonly CommandLineOptions _commandLineOptions = new(); private readonly Mock _mockFileHelper; private readonly Mock _mockOutput; private readonly Mock _mockAssemblyMetadataProvider; @@ -79,14 +80,14 @@ public RunTestsArgumentProcessorTests() [TestMethod] public void GetMetadataShouldReturnRunTestsArgumentProcessorCapabilities() { - RunTestsArgumentProcessor processor = new(CommandLineOptions.Instance, new TestableRunSettingsProvider(), new Mock().Object); + RunTestsArgumentProcessor processor = new(_commandLineOptions, new TestableRunSettingsProvider(), new Mock().Object); Assert.IsTrue(processor.Metadata.Value is RunTestsArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnRunTestsArgumentProcessorCapabilities() { - RunTestsArgumentProcessor processor = new(CommandLineOptions.Instance, new TestableRunSettingsProvider(), new Mock().Object); + RunTestsArgumentProcessor processor = new(_commandLineOptions, new TestableRunSettingsProvider(), new Mock().Object); Assert.IsTrue(processor.Executor!.Value is RunTestsArgumentExecutor); } @@ -118,10 +119,9 @@ public void ExecutorExecuteShouldReturnSuccessWithoutExecutionInDesignMode() var runSettingsProvider = new TestableRunSettingsProvider(); runSettingsProvider.UpdateRunSettings(""); - CommandLineOptions.Reset(); - CommandLineOptions.Instance.IsDesignMode = true; - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); - var executor = new RunTestsArgumentExecutor(CommandLineOptions.Instance, runSettingsProvider, testRequestManager, _artifactProcessingManager.Object, _mockOutput.Object); + _commandLineOptions.IsDesignMode = true; + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); + var executor = new RunTestsArgumentExecutor(_commandLineOptions, runSettingsProvider, testRequestManager, _artifactProcessingManager.Object, _mockOutput.Object); Assert.AreEqual(ArgumentProcessorResult.Success, executor.Execute()); } @@ -129,8 +129,7 @@ public void ExecutorExecuteShouldReturnSuccessWithoutExecutionInDesignMode() [TestMethod] public void ExecutorExecuteForNoSourcesShouldThrowCommandLineException() { - CommandLineOptions.Reset(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Execute()); @@ -141,7 +140,7 @@ private RunTestsArgumentExecutor GetExecutor(ITestRequestManager testRequestMana var runSettingsProvider = new TestableRunSettingsProvider(); runSettingsProvider.AddDefaultRunSettings(); var executor = new RunTestsArgumentExecutor( - CommandLineOptions.Instance, + _commandLineOptions, runSettingsProvider, testRequestManager, _artifactProcessingManager.Object, @@ -160,7 +159,7 @@ public void ExecutorExecuteShouldThrowTestPlatformException() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockTestRunRequest.Object); ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Execute()); @@ -176,7 +175,7 @@ public void ExecutorExecuteShouldThrowSettingsException() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockTestRunRequest.Object); ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Execute()); @@ -192,7 +191,7 @@ public void ExecutorExecuteShouldThrowInvalidOperationException() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockTestRunRequest.Object); ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Execute()); @@ -208,7 +207,7 @@ public void ExecutorExecuteShouldThrowOtherExceptions() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockTestRunRequest.Object); ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Execute()); @@ -265,7 +264,7 @@ private ArgumentProcessorResult RunRunArgumentProcessorExecuteWithMockSetup(ITes ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(CommandLineOptions.Instance, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); return executor.Execute(); @@ -273,11 +272,10 @@ private ArgumentProcessorResult RunRunArgumentProcessorExecuteWithMockSetup(ITes private void ResetAndAddSourceToCommandLineOptions() { - CommandLineOptions.Reset(); - CommandLineOptions.Instance.FileHelper = _mockFileHelper.Object; - CommandLineOptions.Instance.FilePatternParser = new FilePatternParser(new Mock().Object, _mockFileHelper.Object); - CommandLineOptions.Instance.AddSource(_dummyTestFilePath); + _commandLineOptions.FileHelper = _mockFileHelper.Object; + _commandLineOptions.FilePatternParser = new FilePatternParser(new Mock().Object, _mockFileHelper.Object); + _commandLineOptions.AddSource(_dummyTestFilePath); } public static void SetupMockExtensions() diff --git a/test/vstest.console.UnitTests/Processors/TestAdapterLoadingStrategyArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/TestAdapterLoadingStrategyArgumentProcessorTests.cs index daeee438f3..a5945d8784 100644 --- a/test/vstest.console.UnitTests/Processors/TestAdapterLoadingStrategyArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/TestAdapterLoadingStrategyArgumentProcessorTests.cs @@ -22,6 +22,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [DoNotParallelize] public class TestAdapterLoadingStrategyArgumentProcessorTests { + private readonly CommandLineOptions _commandLineOptions = new(); private readonly RunSettings _currentActiveSetting; public TestAdapterLoadingStrategyArgumentProcessorTests() @@ -49,7 +50,7 @@ public void InitializeShouldHonorEnvironmentVariablesInTestAdapterPaths() mockFileHelper.Setup(x => x.DirectoryExists(It.IsAny())).Returns(true); mockFileHelper.Setup(x => x.GetFullPath(It.IsAny())).Returns((Func)(s => Path.GetFullPath(s))); - var executor = new TestAdapterLoadingStrategyArgumentExecutor(CommandLineOptions.Instance, RunSettingsManager.Instance, mockOutput.Object, mockFileHelper.Object); + var executor = new TestAdapterLoadingStrategyArgumentExecutor(_commandLineOptions, RunSettingsManager.Instance, mockOutput.Object, mockFileHelper.Object); executor.Initialize(nameof(TestAdapterLoadingStrategy.Default)); var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(RunSettingsManager.Instance.ActiveRunSettings.SettingsXml); @@ -71,7 +72,7 @@ public void InitializeShouldAddRightAdapterPathInErrorMessage() mockFileHelper.Setup(x => x.DirectoryExists("d:\\users")).Returns(false); mockFileHelper.Setup(x => x.DirectoryExists("c:\\users")).Returns(true); - var executor = new TestAdapterLoadingStrategyArgumentExecutor(CommandLineOptions.Instance, RunSettingsManager.Instance, mockOutput.Object, mockFileHelper.Object); + var executor = new TestAdapterLoadingStrategyArgumentExecutor(_commandLineOptions, RunSettingsManager.Instance, mockOutput.Object, mockFileHelper.Object); var message = "The path 'd:\\users' specified in the 'TestAdapterPath' is invalid. Error: The custom test adapter search path provided was not found, provide a valid path and try again."; @@ -91,7 +92,7 @@ public void InitializeShouldThrowIfPathDoesNotExist() RunSettingsManager.Instance.SetActiveRunSettings(runSettings); var mockOutput = new Mock(); - var executor = new TestAdapterLoadingStrategyArgumentExecutor(CommandLineOptions.Instance, RunSettingsManager.Instance, mockOutput.Object, new FileHelper()); + var executor = new TestAdapterLoadingStrategyArgumentExecutor(_commandLineOptions, RunSettingsManager.Instance, mockOutput.Object, new FileHelper()); var message = $"The path '{folder}' specified in the 'TestAdapterPath' is invalid. Error: The custom test adapter search path provided was not found, provide a valid path and try again."; diff --git a/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs index 77731e299c..816921e443 100644 --- a/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs @@ -26,6 +26,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [DoNotParallelize] public class TestAdapterPathArgumentProcessorTests { + private readonly CommandLineOptions _commandLineOptions = new(); private readonly RunSettings _currentActiveSetting; public TestAdapterPathArgumentProcessorTests() @@ -43,14 +44,14 @@ public void TestClean() [TestMethod] public void GetMetadataShouldReturnTestAdapterPathArgumentProcessorCapabilities() { - var processor = new TestAdapterPathArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); + var processor = new TestAdapterPathArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Metadata.Value is TestAdapterPathArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnTestAdapterPathArgumentProcessorCapabilities() { - var processor = new TestAdapterPathArgumentProcessor(CommandLineOptions.Instance, new TestableRunSettingsProvider()); + var processor = new TestAdapterPathArgumentProcessor(_commandLineOptions, new TestableRunSettingsProvider()); Assert.IsTrue(processor.Executor!.Value is TestAdapterPathArgumentExecutor); } @@ -82,7 +83,7 @@ public void InitializeShouldThrowIfArgumentIsNull() { var mockRunSettingsProvider = new Mock(); var mockOutput = new Mock(); - var executor = new TestAdapterPathArgumentExecutor(CommandLineOptions.Instance, mockRunSettingsProvider.Object, mockOutput.Object, new FileHelper()); + var executor = new TestAdapterPathArgumentExecutor(_commandLineOptions, mockRunSettingsProvider.Object, mockOutput.Object, new FileHelper()); var message = @"The /TestAdapterPath parameter requires a value, which is path of a location containing custom test adapters. Example: /TestAdapterPath:c:\MyCustomAdapters"; @@ -96,7 +97,7 @@ public void InitializeShouldThrowIfArgumentIsAWhiteSpace() { var mockRunSettingsProvider = new Mock(); var mockOutput = new Mock(); - var executor = new TestAdapterPathArgumentExecutor(CommandLineOptions.Instance, mockRunSettingsProvider.Object, mockOutput.Object, new FileHelper()); + var executor = new TestAdapterPathArgumentExecutor(_commandLineOptions, mockRunSettingsProvider.Object, mockOutput.Object, new FileHelper()); var message = @"The /TestAdapterPath parameter requires a value, which is path of a location containing custom test adapters. Example: /TestAdapterPath:c:\MyCustomAdapters"; @@ -111,7 +112,7 @@ public void InitializeShouldUpdateTestAdapterPathInRunSettings() RunSettingsManager.Instance.AddDefaultRunSettings(); var mockOutput = new Mock(); - var executor = new TestAdapterPathArgumentExecutor(CommandLineOptions.Instance, RunSettingsManager.Instance, mockOutput.Object, new FileHelper()); + var executor = new TestAdapterPathArgumentExecutor(_commandLineOptions, RunSettingsManager.Instance, mockOutput.Object, new FileHelper()); var currentAssemblyPath = typeof(TestAdapterPathArgumentExecutor).Assembly.Location; var currentFolder = Path.GetDirectoryName(currentAssemblyPath); @@ -133,7 +134,7 @@ public void InitializeShouldMergeTestAdapterPathsInRunSettings() var mockOutput = new Mock(); mockFileHelper.Setup(x => x.DirectoryExists(It.IsAny())).Returns(true); - var executor = new TestAdapterPathArgumentExecutor(CommandLineOptions.Instance, RunSettingsManager.Instance, mockOutput.Object, mockFileHelper.Object); + var executor = new TestAdapterPathArgumentExecutor(_commandLineOptions, RunSettingsManager.Instance, mockOutput.Object, mockFileHelper.Object); executor.Initialize("c:\\users"); var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(RunSettingsManager.Instance.ActiveRunSettings.SettingsXml); @@ -152,7 +153,7 @@ public void InitializeShouldTrimTrailingAndLeadingDoubleQuotes() var mockOutput = new Mock(); mockFileHelper.Setup(x => x.DirectoryExists(It.IsAny())).Returns(true); - var executor = new TestAdapterPathArgumentExecutor(CommandLineOptions.Instance, RunSettingsManager.Instance, mockOutput.Object, mockFileHelper.Object); + var executor = new TestAdapterPathArgumentExecutor(_commandLineOptions, RunSettingsManager.Instance, mockOutput.Object, mockFileHelper.Object); executor.Initialize("\"c:\\users\""); var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(RunSettingsManager.Instance.ActiveRunSettings.SettingsXml); diff --git a/test/vstest.console.UnitTests/Processors/TestCaseFilterArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/TestCaseFilterArgumentProcessorTests.cs index 389eb56d91..758fac0607 100644 --- a/test/vstest.console.UnitTests/Processors/TestCaseFilterArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/TestCaseFilterArgumentProcessorTests.cs @@ -11,17 +11,18 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] public class TestCaseFilterArgumentProcessorTests { + private readonly CommandLineOptions _commandLineOptions = new(); [TestMethod] public void GetMetadataShouldReturnTestCaseFilterArgumentProcessorCapabilities() { - TestCaseFilterArgumentProcessor processor = new(CommandLineOptions.Instance); + TestCaseFilterArgumentProcessor processor = new(_commandLineOptions); Assert.IsTrue(processor.Metadata.Value is TestCaseFilterArgumentProcessorCapabilities); } [TestMethod] public void GetExecutorShouldReturnTestCaseFilterArgumentProcessorCapabilities() { - TestCaseFilterArgumentProcessor processor = new(CommandLineOptions.Instance); + TestCaseFilterArgumentProcessor processor = new(_commandLineOptions); Assert.IsTrue(processor.Executor!.Value is TestCaseFilterArgumentExecutor); } @@ -48,7 +49,7 @@ public void CapabilitiesShouldAppropriateProperties() [TestMethod] public void ExecutorInitializeWithNullOrEmptyTestCaseFilterShouldThrowCommandLineException() { - var options = CommandLineOptions.Instance; + var options = new CommandLineOptions(); TestCaseFilterArgumentExecutor executor = new(options); var ex = Assert.ThrowsExactly(() => executor.Initialize(null)); @@ -58,7 +59,7 @@ public void ExecutorInitializeWithNullOrEmptyTestCaseFilterShouldThrowCommandLin [TestMethod] public void ExecutorInitializeWithNullOrEmptyTestCaseFilterShouldNotThrowWhenTestFilterWasSpecifiedByPreviousStep() { - var options = CommandLineOptions.Instance; + var options = new CommandLineOptions(); options.TestCaseFilterValue = "Test=FilterFromPreviousStep"; TestCaseFilterArgumentExecutor executor = new(options); @@ -68,7 +69,7 @@ public void ExecutorInitializeWithNullOrEmptyTestCaseFilterShouldNotThrowWhenTes [TestMethod] public void ExecutorInitializeWithTestCaseFilterShouldMergeWithTheValueProvidedByPreviousStep() { - var options = CommandLineOptions.Instance; + var options = new CommandLineOptions(); var defaultValue = "Test=FilterFromPreviousStep"; options.TestCaseFilterValue = defaultValue; Assert.AreEqual(defaultValue, options.TestCaseFilterValue); @@ -84,7 +85,7 @@ public void ExecutorInitializeWithTestCaseFilterShouldMergeWithTheValueProvidedB [TestMethod] public void ExecutorExecutoreturnArgumentProcessorResultSuccess() { - var executor = new TestCaseFilterArgumentExecutor(CommandLineOptions.Instance); + var executor = new TestCaseFilterArgumentExecutor(_commandLineOptions); var result = executor.Execute(); Assert.AreEqual(ArgumentProcessorResult.Success, result); } diff --git a/test/vstest.console.UnitTests/Processors/TestSourceArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/TestSourceArgumentProcessorTests.cs index 4b65032857..bf72e7021e 100644 --- a/test/vstest.console.UnitTests/Processors/TestSourceArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/TestSourceArgumentProcessorTests.cs @@ -20,13 +20,14 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] public class TestSourceArgumentProcessorTests { + private readonly CommandLineOptions _commandLineOptions = new(); /// /// The help argument processor get metadata should return help argument processor capabilities. /// [TestMethod] public void GetMetadataShouldReturnTestSourceArgumentProcessorCapabilities() { - TestSourceArgumentProcessor processor = new(CommandLineOptions.Instance); + TestSourceArgumentProcessor processor = new(_commandLineOptions); Assert.IsTrue(processor.Metadata.Value is TestSourceArgumentProcessorCapabilities); } @@ -36,7 +37,7 @@ public void GetMetadataShouldReturnTestSourceArgumentProcessorCapabilities() [TestMethod] public void GetExecuterShouldReturnTestSourceArgumentProcessorCapabilities() { - TestSourceArgumentProcessor processor = new(CommandLineOptions.Instance); + TestSourceArgumentProcessor processor = new(_commandLineOptions); Assert.IsTrue(processor.Executor!.Value is TestSourceArgumentExecutor); } @@ -65,7 +66,7 @@ public void CapabilitiesShouldReturnAppropriateProperties() [TestMethod] public void ExecuterInitializeWithInvalidSourceShouldThrowCommandLineException() { - var options = CommandLineOptions.Instance; + var options = new CommandLineOptions(); var mockFileHelper = new Mock(); mockFileHelper.Setup(x => x.GetCurrentDirectory()).Returns(""); options.FileHelper = mockFileHelper.Object; @@ -87,8 +88,7 @@ public void ExecuterInitializeWithValidSourceShouldAddItToTestSources() mockFileHelper.Setup(fh => fh.Exists(testFilePath)).Returns(true); mockFileHelper.Setup(x => x.GetCurrentDirectory()).Returns(""); - var options = CommandLineOptions.Instance; - CommandLineOptions.Reset(); + var options = new CommandLineOptions(); options.FileHelper = mockFileHelper.Object; options.FilePatternParser = new FilePatternParser(new Mock().Object, mockFileHelper.Object); var executor = new TestSourceArgumentExecutor(options); @@ -102,7 +102,7 @@ public void ExecuterInitializeWithValidSourceShouldAddItToTestSources() [TestMethod] public void ExecutorExecuteReturnArgumentProcessorResultSuccess() { - var options = CommandLineOptions.Instance; + var options = new CommandLineOptions(); var executor = new TestSourceArgumentExecutor(options); var result = executor.Execute(); Assert.AreEqual(ArgumentProcessorResult.Success, result); diff --git a/test/vstest.console.UnitTests/Processors/UseVsixExtensionsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/UseVsixExtensionsArgumentProcessorTests.cs index de13427eac..073f0a05b4 100644 --- a/test/vstest.console.UnitTests/Processors/UseVsixExtensionsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/UseVsixExtensionsArgumentProcessorTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; @@ -17,6 +17,7 @@ namespace vstest.console.UnitTests.Processors; [TestClass] public class UseVsixExtensionsArgumentProcessorTests { + private readonly CommandLineOptions _commandLineOptions = new(); private const string DeprecationMessage = @"/UseVsixExtensions is getting deprecated. Please use /TestAdapterPath instead."; private readonly Mock _testRequestManager; private readonly Mock _extensionManager; @@ -28,20 +29,20 @@ public UseVsixExtensionsArgumentProcessorTests() _testRequestManager = new Mock(); _extensionManager = new Mock(); _output = new Mock(); - _executor = new UseVsixExtensionsArgumentExecutor(CommandLineOptions.Instance, _testRequestManager.Object, _extensionManager.Object, _output.Object); + _executor = new UseVsixExtensionsArgumentExecutor(_commandLineOptions, _testRequestManager.Object, _extensionManager.Object, _output.Object); } [TestMethod] public void GetMetadataShouldReturnUseVsixExtensionsArgumentProcessorCapabilities() { - var processor = new UseVsixExtensionsArgumentProcessor(CommandLineOptions.Instance, _testRequestManager.Object); + var processor = new UseVsixExtensionsArgumentProcessor(_commandLineOptions, _testRequestManager.Object); Assert.IsTrue(processor.Metadata.Value is UseVsixExtensionsArgumentProcessorCapabilities); } [TestMethod] public void GetExecuterShouldReturnUseVsixExtensionsArgumentProcessorCapabilities() { - var processor = new UseVsixExtensionsArgumentProcessor(CommandLineOptions.Instance, _testRequestManager.Object); + var processor = new UseVsixExtensionsArgumentProcessor(_commandLineOptions, _testRequestManager.Object); Assert.IsTrue(processor.Executor!.Value is UseVsixExtensionsArgumentExecutor); } diff --git a/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs b/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs index 801003edf4..725d3b4b8b 100644 --- a/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs +++ b/test/vstest.console.UnitTests/Processors/Utilities/ArgumentProcessorFactoryTests.cs @@ -174,7 +174,7 @@ private static IEnumerable GetArgumentProcessors(bool specia // optionally followed by an IRunSettingsProvider and/or IRunSettingsHelper, and the run/discovery // ones also take an ITestRequestManager; a few legacy ones take only a run settings dependency, and // the rest are parameterless. Try the known shapes from most to least specific. - var commandLineOptions = CommandLineOptions.Instance; + var commandLineOptions = new CommandLineOptions(); var runSettingsProvider = new TestableRunSettingsProvider(); var runSettingsHelper = new RunSettingsHelper(); var testRequestManager = new Mock().Object; diff --git a/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs b/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs index 0749c933e4..3a9cae7cf9 100644 --- a/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs +++ b/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs @@ -118,7 +118,6 @@ public TestRequestManagerTests() [TestCleanup] public void Cleanup() { - CommandLineOptions.Reset(); // Opt out the Telemetry Environment.SetEnvironmentVariable("VSTEST_TELEMETRY_OPTEDIN", "0"); @@ -127,9 +126,9 @@ public void Cleanup() [TestMethod] public void TestRequestManagerShouldNotInitializeConsoleLoggerIfDesignModeIsSet() { - CommandLineOptions.Instance.IsDesignMode = true; + _commandLineOptions.IsDesignMode = true; _mockLoggerEvents = new DummyLoggerEvents(TestSessionMessageLogger.Instance); - _ = new TestRequestManager(CommandLineOptions.Instance, + _ = new TestRequestManager(_commandLineOptions, new Mock().Object, TestRunResultAggregator.Instance, new Mock().Object, @@ -156,10 +155,10 @@ public void InitializeExtensionsShouldCallTestPlatformToClearAndUpdateExtensions [TestMethod] public void ResetShouldResetCommandLineOptionsInstance() { - var oldInstance = CommandLineOptions.Instance; + var oldInstance = new CommandLineOptions(); _testRequestManager.ResetOptions(); - var newInstance = CommandLineOptions.Instance; + var newInstance = new CommandLineOptions(); Assert.AreNotEqual(oldInstance, newInstance, "CommandLineOptions must be cleaned up"); } @@ -210,8 +209,8 @@ public void DiscoverTestsShouldCallTestPlatformAndSucceed() var mockDiscoveryRegistrar = new Mock(); string testCaseFilterValue = "TestFilter"; - CommandLineOptions.Instance.TestCaseFilterValue = testCaseFilterValue; - _testRequestManager = new TestRequestManager(CommandLineOptions.Instance, + _commandLineOptions.TestCaseFilterValue = testCaseFilterValue; + _testRequestManager = new TestRequestManager(_commandLineOptions, _mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, @@ -262,8 +261,8 @@ public void DiscoverTestsShouldPassSameProtocolConfigInRequestData() var mockDiscoveryRegistrar = new Mock(); string testCaseFilterValue = "TestFilter"; - CommandLineOptions.Instance.TestCaseFilterValue = testCaseFilterValue; - _testRequestManager = new TestRequestManager(CommandLineOptions.Instance, + _commandLineOptions.TestCaseFilterValue = testCaseFilterValue; + _testRequestManager = new TestRequestManager(_commandLineOptions, _mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, @@ -313,7 +312,7 @@ public void DiscoverTestsShouldCollectMetrics() (IRequestData requestData, DiscoveryCriteria discoveryCriteria, TestPlatformOptions options, Dictionary sourceToSourceDetailMap, IWarningLogger _) => actualRequestData = requestData).Returns(mockDiscoveryRequest.Object); _testRequestManager = new TestRequestManager( - CommandLineOptions.Instance, + _commandLineOptions, _mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, @@ -364,7 +363,7 @@ public void DiscoverTestsShouldCollectTargetDeviceLocalMachineIfTargetDeviceStri (IRequestData requestData, DiscoveryCriteria discoveryCriteria, TestPlatformOptions options, Dictionary sourceToSourceDetailMap, IWarningLogger _) => actualRequestData = requestData).Returns(mockDiscoveryRequest.Object); _testRequestManager = new TestRequestManager( - CommandLineOptions.Instance, + _commandLineOptions, _mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, @@ -409,7 +408,7 @@ public void DiscoverTestsShouldCollectTargetDeviceIfTargetDeviceIsDevice() (IRequestData requestData, DiscoveryCriteria discoveryCriteria, TestPlatformOptions options, Dictionary sourceToSourceDetailMap, IWarningLogger _) => actualRequestData = requestData).Returns(mockDiscoveryRequest.Object); _testRequestManager = new TestRequestManager( - CommandLineOptions.Instance, + _commandLineOptions, _mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, @@ -454,7 +453,7 @@ public void DiscoverTestsShouldCollectTargetDeviceIfTargetDeviceIsEmulator() (IRequestData requestData, DiscoveryCriteria discoveryCriteria, TestPlatformOptions options, Dictionary sourceToSourceDetailMap, IWarningLogger _) => actualRequestData = requestData).Returns(mockDiscoveryRequest.Object); _testRequestManager = new TestRequestManager( - CommandLineOptions.Instance, + _commandLineOptions, _mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, @@ -499,7 +498,7 @@ public void DiscoverTestsShouldCollectCommands() (IRequestData requestData, DiscoveryCriteria discoveryCriteria, TestPlatformOptions options, Dictionary sourceToSourceDetailMap, IWarningLogger _) => actualRequestData = requestData).Returns(mockDiscoveryRequest.Object); _testRequestManager = new TestRequestManager( - CommandLineOptions.Instance, + _commandLineOptions, _mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, @@ -510,11 +509,11 @@ public void DiscoverTestsShouldCollectCommands() _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); - CommandLineOptions.Instance.Parallel = true; - CommandLineOptions.Instance.EnableCodeCoverage = true; - CommandLineOptions.Instance.InIsolation = true; - CommandLineOptions.Instance.UseVsixExtensions = true; - CommandLineOptions.Instance.SettingsFile = @"c://temp/.runsettings"; + _commandLineOptions.Parallel = true; + _commandLineOptions.EnableCodeCoverage = true; + _commandLineOptions.InIsolation = true; + _commandLineOptions.UseVsixExtensions = true; + _commandLineOptions.SettingsFile = @"c://temp/.runsettings"; // Act _testRequestManager.DiscoverTests(payload, mockDiscoveryRegistrar.Object, mockProtocolConfig); @@ -556,7 +555,7 @@ public void DiscoverTestsShouldCollectTestSettings() (IRequestData requestData, DiscoveryCriteria discoveryCriteria, TestPlatformOptions options, Dictionary sourceToSourceDetailMap, IWarningLogger _) => actualRequestData = requestData).Returns(mockDiscoveryRequest.Object); _testRequestManager = new TestRequestManager( - CommandLineOptions.Instance, + _commandLineOptions, _mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, @@ -567,7 +566,7 @@ public void DiscoverTestsShouldCollectTestSettings() _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); - CommandLineOptions.Instance.SettingsFile = @"c://temp/.testsettings"; + _commandLineOptions.SettingsFile = @"c://temp/.testsettings"; // Act _testRequestManager.DiscoverTests(payload, mockDiscoveryRegistrar.Object, mockProtocolConfig); @@ -604,7 +603,7 @@ public void DiscoverTestsShouldCollectVsmdiFile() (IRequestData requestData, DiscoveryCriteria discoveryCriteria, TestPlatformOptions options, Dictionary sourceToSourceDetailMap, IWarningLogger _) => actualRequestData = requestData).Returns(mockDiscoveryRequest.Object); _testRequestManager = new TestRequestManager( - CommandLineOptions.Instance, + _commandLineOptions, _mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, @@ -615,7 +614,7 @@ public void DiscoverTestsShouldCollectVsmdiFile() _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); - CommandLineOptions.Instance.SettingsFile = @"c://temp/.vsmdi"; + _commandLineOptions.SettingsFile = @"c://temp/.vsmdi"; // Act _testRequestManager.DiscoverTests(payload, mockDiscoveryRegistrar.Object, mockProtocolConfig); @@ -652,7 +651,7 @@ public void DiscoverTestsShouldCollectTestRunConfigFile() (IRequestData requestData, DiscoveryCriteria discoveryCriteria, TestPlatformOptions options, Dictionary sourceToSourceDetailMap, IWarningLogger _) => actualRequestData = requestData).Returns(mockDiscoveryRequest.Object); _testRequestManager = new TestRequestManager( - CommandLineOptions.Instance, + _commandLineOptions, _mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, @@ -663,7 +662,7 @@ public void DiscoverTestsShouldCollectTestRunConfigFile() _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); - CommandLineOptions.Instance.SettingsFile = @"c://temp/.testrunConfig"; + _commandLineOptions.SettingsFile = @"c://temp/.testrunConfig"; // Act _testRequestManager.DiscoverTests(payload, mockDiscoveryRegistrar.Object, mockProtocolConfig); @@ -956,7 +955,7 @@ public void RunTestsShouldCollectCommands() (IRequestData requestData, TestRunCriteria runCriteria, TestPlatformOptions options, Dictionary sourceToSourceDetailMap, IWarningLogger _) => actualRequestData = requestData).Returns(mockDiscoveryRequest.Object); _testRequestManager = new TestRequestManager( - CommandLineOptions.Instance, + _commandLineOptions, _mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, @@ -967,11 +966,11 @@ public void RunTestsShouldCollectCommands() _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); - CommandLineOptions.Instance.Parallel = true; - CommandLineOptions.Instance.EnableCodeCoverage = true; - CommandLineOptions.Instance.InIsolation = true; - CommandLineOptions.Instance.UseVsixExtensions = true; - CommandLineOptions.Instance.SettingsFile = @"c://temp/.runsettings"; + _commandLineOptions.Parallel = true; + _commandLineOptions.EnableCodeCoverage = true; + _commandLineOptions.InIsolation = true; + _commandLineOptions.UseVsixExtensions = true; + _commandLineOptions.SettingsFile = @"c://temp/.runsettings"; // Act. _testRequestManager.RunTests(payload, new Mock().Object, new Mock().Object, mockProtocolConfig); @@ -1022,7 +1021,7 @@ public void RunTestsShouldCollectTelemetryForLegacySettings() (IRequestData requestData, TestRunCriteria runCriteria, TestPlatformOptions options, Dictionary sourceToSourceDetailMap, IWarningLogger _) => actualRequestData = requestData).Returns(mockDiscoveryRequest.Object); _testRequestManager = new TestRequestManager( - CommandLineOptions.Instance, + _commandLineOptions, _mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, @@ -1071,7 +1070,7 @@ public void RunTestsShouldCollectTelemetryForTestSettingsEmbeddedInsideRunSettin (IRequestData requestData, TestRunCriteria runCriteria, TestPlatformOptions options, Dictionary sourceToSourceDetailMap, IWarningLogger _) => actualRequestData = requestData).Returns(mockDiscoveryRequest.Object); _testRequestManager = new TestRequestManager( - CommandLineOptions.Instance, + _commandLineOptions, _mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, @@ -1118,7 +1117,7 @@ public void RunTestsShouldCollectMetrics() (IRequestData requestData, TestRunCriteria runCriteria, TestPlatformOptions options, Dictionary sourceToSourceDetailMap, IWarningLogger _) => actualRequestData = requestData).Returns(mockDiscoveryRequest.Object); _testRequestManager = new TestRequestManager( - CommandLineOptions.Instance, + _commandLineOptions, _mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, @@ -1167,7 +1166,7 @@ public void RunTestsWithSourcesShouldCallTestPlatformAndSucceed() string testCaseFilterValue = "TestFilter"; payload.TestPlatformOptions = new TestPlatformOptions { TestCaseFilter = testCaseFilterValue }; - _testRequestManager = new TestRequestManager(CommandLineOptions.Instance, + _testRequestManager = new TestRequestManager(_commandLineOptions, _mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, @@ -2614,10 +2613,10 @@ public void WritingTestCaseFilterThroughArgumentExecutorIsObservedByTestRequestM // separate copies. const string filter = "FullyQualifiedName~SharedInstanceMarker"; - // The process-wide static default is a different object. Capturing it up front lets us prove the write lands on - // the injected instance only, and that a reader bound to the static default observes none of it. - var staticDefault = CommandLineOptions.Instance; - staticDefault.Should().NotBeSameAs(_commandLineOptions, "the manager under test was injected with a separate CommandLineOptions instance"); + // A separate instance is a different object. Capturing it up front lets us prove the write lands on + // the injected instance only, and that a reader bound to the separate instance observes none of it. + var separateInstance = new CommandLineOptions(); + separateInstance.Should().NotBeSameAs(_commandLineOptions, "the manager under test was injected with a separate CommandLineOptions instance"); var payload = new DiscoveryRequestPayload() { @@ -2635,7 +2634,7 @@ public void WritingTestCaseFilterThroughArgumentExecutorIsObservedByTestRequestM // Writer: parsing "--TestCaseFilter " sets TestCaseFilterValue on the injected instance and nowhere else. new TestCaseFilterArgumentExecutor(_commandLineOptions).Initialize(filter); _commandLineOptions.TestCaseFilterValue.Should().Be(filter, "the executor writes the filter on the injected instance"); - staticDefault.TestCaseFilterValue.Should().BeNull("the write must not leak onto the static default instance"); + separateInstance.TestCaseFilterValue.Should().BeNull("the write must not leak onto the separate instance"); // Reader: the manager copies TestCaseFilterValue from the same injected instance onto the DiscoveryCriteria. _testRequestManager.DiscoverTests(payload, new Mock().Object, _protocolConfig); @@ -2644,17 +2643,17 @@ public void WritingTestCaseFilterThroughArgumentExecutorIsObservedByTestRequestM observedCriteria.Should().NotBeNull(); observedCriteria!.TestCaseFilter.Should().Be(filter, "the reader observed the writer's value through the shared instance"); - // Reader-vs-reader: a second manager bound to the static default instance (which never received the write) must + // Reader-vs-reader: a second manager bound to the separate instance (which never received the write) must // NOT observe the filter - it falls back to the run settings, which carry none, so the criteria filter is null. - DiscoveryCriteria? staticDefaultCriteria = null; + DiscoveryCriteria? separateInstanceCriteria = null; var mockTestPlatformForStaticDefault = new Mock(); mockTestPlatformForStaticDefault.Setup(mt => mt.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())) .Callback((IRequestData _, DiscoveryCriteria criteria, TestPlatformOptions _, Dictionary _, IWarningLogger _) => - staticDefaultCriteria = criteria) + separateInstanceCriteria = criteria) .Returns(new Mock().Object); var managerBoundToStaticDefault = new TestRequestManager( - staticDefault, + separateInstance, mockTestPlatformForStaticDefault.Object, new DummyTestRunResultAggregator(), _mockTestPlatformEventSource.Object, @@ -2668,8 +2667,8 @@ public void WritingTestCaseFilterThroughArgumentExecutorIsObservedByTestRequestM managerBoundToStaticDefault.DiscoverTests(payload, new Mock().Object, _protocolConfig); - staticDefaultCriteria.Should().NotBeNull(); - staticDefaultCriteria!.TestCaseFilter.Should().BeNull("the manager bound to the static default instance never saw the write"); + separateInstanceCriteria.Should().NotBeNull(); + separateInstanceCriteria!.TestCaseFilter.Should().BeNull("the manager bound to the separate instance never saw the write"); } [TestMethod] From 2350e5a1057160be8fa2fd2424f4120c3ce047af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Fri, 10 Jul 2026 16:43:26 +0200 Subject: [PATCH 56/87] Delete the TestRunResultAggregator.Instance singleton (#16262) vstest.console shared one process-wide TestRunResultAggregator through the TestRunResultAggregator.Instance static. Executor read its Outcome for the exit code and TestRequestManager registered runs against it, both reaching for the same static. Executor already owns the aggregator (injected in #16245); this threads that same instance into the TestRequestManager it builds, so the reader and the writer share one request-scoped aggregator without the static. The factory fallback (the Help path, which never runs tests) builds its own fresh instance. TestRunResultAggregator is internal, so nothing outside vstest.console could read the singleton. With the last readers injected, Instance and s_instance are gone. Tests build their own aggregator instead of the static. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/vstest.console/CommandLine/Executor.cs | 6 +-- .../CommandLine/TestRunResultAggregator.cs | 10 ----- .../Utilities/ArgumentProcessorFactory.cs | 2 +- .../TestPlatformHelpers/TestRequestManager.cs | 4 +- .../TestRunResultAggregatorTests.cs | 2 +- .../ExecutorUnitTests.cs | 21 ++++----- ...llyQualifiedTestsArgumentProcessorTests.cs | 16 +++---- .../ListTestsArgumentProcessorTests.cs | 14 +++--- .../RunSpecificTestsArgumentProcessorTests.cs | 44 +++++++++---------- .../RunTestsArgumentProcessorTests.cs | 14 +++--- .../TestRequestManagerTests.cs | 32 +++++++------- 11 files changed, 76 insertions(+), 89 deletions(-) diff --git a/src/vstest.console/CommandLine/Executor.cs b/src/vstest.console/CommandLine/Executor.cs index 12b69a9c4d..3ce196ecd6 100644 --- a/src/vstest.console/CommandLine/Executor.cs +++ b/src/vstest.console/CommandLine/Executor.cs @@ -101,12 +101,12 @@ internal class Executor } internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSource, IProcessHelper processHelper, IEnvironment environment) - : this(output, testPlatformEventSource, processHelper, environment, RunSettingsManager.Instance, RunSettingsHelper.Instance, new CommandLineOptions(), TestRunResultAggregator.Instance) + : this(output, testPlatformEventSource, processHelper, environment, RunSettingsManager.Instance, RunSettingsHelper.Instance, new CommandLineOptions(), new TestRunResultAggregator()) { } internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSource, IProcessHelper processHelper, IEnvironment environment, IRunSettingsProvider runSettingsProvider) - : this(output, testPlatformEventSource, processHelper, environment, runSettingsProvider, RunSettingsHelper.Instance, new CommandLineOptions(), TestRunResultAggregator.Instance) + : this(output, testPlatformEventSource, processHelper, environment, runSettingsProvider, RunSettingsHelper.Instance, new CommandLineOptions(), new TestRunResultAggregator()) { } @@ -125,7 +125,7 @@ internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSour _runSettingsHelper = runSettingsHelper; _commandLineOptions = commandLineOptions; _testRunResultAggregator = testRunResultAggregator; - _testRequestManager = testRequestManager ?? new LazyTestRequestManager(() => new TestRequestManager(_commandLineOptions)); + _testRequestManager = testRequestManager ?? new LazyTestRequestManager(() => new TestRequestManager(_commandLineOptions, _testRunResultAggregator)); } /// diff --git a/src/vstest.console/CommandLine/TestRunResultAggregator.cs b/src/vstest.console/CommandLine/TestRunResultAggregator.cs index 77f55fe667..c31e23b3f6 100644 --- a/src/vstest.console/CommandLine/TestRunResultAggregator.cs +++ b/src/vstest.console/CommandLine/TestRunResultAggregator.cs @@ -12,25 +12,15 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine; /// internal class TestRunResultAggregator { - private static TestRunResultAggregator? s_instance; - /// /// Initializes the TestRunResultAggregator /// - /// Constructor is private since the factory method should be used to get the instance. protected internal TestRunResultAggregator() { // Outcome is passed until we see a failure. Outcome = TestOutcome.Passed; } - /// - /// Gets the instance of the test run result aggregator. - /// - /// Instance of the test run result aggregator. - public static TestRunResultAggregator Instance - => s_instance ??= new TestRunResultAggregator(); - /// /// The current test run outcome. /// diff --git a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs index 5eaf092148..05551606a5 100644 --- a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs +++ b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs @@ -77,7 +77,7 @@ internal static ArgumentProcessorFactory Create(IFeatureFlag? featureFlag = null runSettingsProvider ??= RunSettingsManager.Instance; runSettingsHelper ??= RunSettingsHelper.Instance; commandLineOptions ??= new CommandLineOptions(); - testRequestManager ??= new LazyTestRequestManager(() => new TestRequestManager(commandLineOptions)); + testRequestManager ??= new LazyTestRequestManager(() => new TestRequestManager(commandLineOptions, new TestRunResultAggregator())); var defaultArgumentProcessor = GetDefaultArgumentProcessors(runSettingsProvider, runSettingsHelper, commandLineOptions, testRequestManager); if (!(featureFlag ?? FeatureFlag.Instance).IsSet(FeatureFlag.VSTEST_DISABLE_ARTIFACTS_POSTPROCESSING)) diff --git a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs index d1ded4836e..c7be08919d 100644 --- a/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs +++ b/src/vstest.console/TestPlatformHelpers/TestRequestManager.cs @@ -91,11 +91,11 @@ internal class TestRequestManager : ITestRequestManager private CancellationTokenSource? _currentAttachmentsProcessingCancellationTokenSource; - internal TestRequestManager(CommandLineOptions commandLineOptions) + internal TestRequestManager(CommandLineOptions commandLineOptions, TestRunResultAggregator testRunResultAggregator) : this( commandLineOptions, TestPlatformFactory.GetTestPlatform(), - TestRunResultAggregator.Instance, + testRunResultAggregator, TestPlatformEventSource.Instance, new InferHelper(AssemblyMetadataProvider.Instance), MetricsPublisherFactory.GetMetricsPublisher( diff --git a/test/vstest.console.UnitTests/CommandLine/TestRunResultAggregatorTests.cs b/test/vstest.console.UnitTests/CommandLine/TestRunResultAggregatorTests.cs index b00db755b2..960a505f0c 100644 --- a/test/vstest.console.UnitTests/CommandLine/TestRunResultAggregatorTests.cs +++ b/test/vstest.console.UnitTests/CommandLine/TestRunResultAggregatorTests.cs @@ -17,7 +17,7 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.CommandLine; [TestClass] public class TestRunResultAggregatorTests { - private readonly TestRunResultAggregator _resultAggregator = TestRunResultAggregator.Instance; + private readonly TestRunResultAggregator _resultAggregator = new TestRunResultAggregator(); private readonly Mock _mockTestRunRequest; public TestRunResultAggregatorTests() diff --git a/test/vstest.console.UnitTests/ExecutorUnitTests.cs b/test/vstest.console.UnitTests/ExecutorUnitTests.cs index 9a598e869e..061ee364f6 100644 --- a/test/vstest.console.UnitTests/ExecutorUnitTests.cs +++ b/test/vstest.console.UnitTests/ExecutorUnitTests.cs @@ -368,19 +368,15 @@ public void MarkingTestRunFailedOnInjectedAggregatorIsObservedByExecutorExitCode // The exit code produced at the end of Executor.Execute is OR-ed with the outcome of the // TestRunResultAggregator (Executor.cs: exitCode |= (Outcome == Passed) ? 0 : 1). This test // proves the reader (Executor) observes the SAME aggregator instance it was constructed with, - // not the process-wide TestRunResultAggregator.Instance static. + // and that separate aggregator instances are isolated from one another (no shared state). // // "--help" is a zero-baseline path: HelpArgumentProcessor runs first and returns Abort, which // does not set the exit bit (only Fail does), so the aggregator's outcome is the sole // contributor to the final exit code. That makes the two outcomes below decisively distinct. - // Baseline the shared static so the negative control is deterministic. - TestRunResultAggregator.Instance.Reset(); - - // Writer: mark a failure on an injected aggregator that is a different instance from the static. + // Writer: mark a failure on the injected aggregator. var injectedAggregator = new DummyTestRunResultAggregator(); injectedAggregator.MarkTestRunFailed(); - Assert.AreNotSame(TestRunResultAggregator.Instance, injectedAggregator); // Reader observes the write through the injected instance: Failed outcome sets the exit bit. var exitCodeWithInjected = new Executor( @@ -395,9 +391,10 @@ public void MarkingTestRunFailedOnInjectedAggregatorIsObservedByExecutorExitCode Assert.AreEqual(1, exitCodeWithInjected, "Executor must observe the injected aggregator's Failed outcome."); - // Negative control: an Executor bound to the static default (still Passed) yields a zero exit - // for the same args, and the write above did not leak onto the static instance. - var exitCodeWithStatic = new Executor( + // Negative control: an Executor bound to a separate, default aggregator (still Passed) yields a + // zero exit for the same args, and the write above did not leak onto this other instance. + var defaultAggregator = new TestRunResultAggregator(); + var exitCodeWithDefault = new Executor( new MockOutput(), _mockTestPlatformEventSource.Object, new ProcessHelper(), @@ -405,10 +402,10 @@ public void MarkingTestRunFailedOnInjectedAggregatorIsObservedByExecutorExitCode RunSettingsManager.Instance, RunSettingsHelper.Instance, _commandLineOptions, - TestRunResultAggregator.Instance).Execute("--help"); + defaultAggregator).Execute("--help"); - Assert.AreEqual(0, exitCodeWithStatic, "The static default aggregator is still Passed, so its Executor must not set the failure bit."); - Assert.AreEqual(TestOutcome.Passed, TestRunResultAggregator.Instance.Outcome, "Marking the injected aggregator failed must not leak onto the static instance."); + Assert.AreEqual(0, exitCodeWithDefault, "A separate default aggregator is still Passed, so its Executor must not set the failure bit."); + Assert.AreEqual(TestOutcome.Passed, defaultAggregator.Outcome, "Marking the injected aggregator failed must not leak onto other aggregator instances."); } private class MockOutput : IOutput diff --git a/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs index 7d9a7840f7..81fb48f61b 100644 --- a/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/ListFullyQualifiedTestsArgumentProcessorTests.cs @@ -133,7 +133,7 @@ public void ExecutorInitializeWithValidSourceShouldAddItToTestSources() { _commandLineOptions.FileHelper = _mockFileHelper.Object; _commandLineOptions.FilePatternParser = new FilePatternParser(new Mock().Object, _mockFileHelper.Object); - var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager, null); executor.Initialize(_dummyTestFilePath); @@ -144,7 +144,7 @@ public void ExecutorInitializeWithValidSourceShouldAddItToTestSources() [TestMethod] public void ExecutorExecuteForNoSourcesShouldReturnFail() { - var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager, null); Assert.ThrowsExactly(() => executor.Execute()); @@ -161,7 +161,7 @@ public void ExecutorExecuteShouldThrowTestPlatformException() ResetAndAddSourceToCommandLineOptions(true); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager, null); @@ -178,7 +178,7 @@ public void ExecutorExecuteShouldThrowSettingsException() mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); ResetAndAddSourceToCommandLineOptions(true); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var listTestsArgumentExecutor = GetExecutor(testRequestManager, null); @@ -197,7 +197,7 @@ public void ExecutorExecuteShouldThrowInvalidOperationException() ResetAndAddSourceToCommandLineOptions(true); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var listTestsArgumentExecutor = GetExecutor(testRequestManager, null); @@ -215,7 +215,7 @@ public void ExecutorExecuteShouldThrowOtherExceptions() ResetAndAddSourceToCommandLineOptions(true); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager, null); @@ -306,7 +306,7 @@ private void RunListFullyQualifiedTestArgumentProcessorWithTraits(Mock().Object, _mockFileHelper.Object); - var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager, null); executor.Initialize(_dummyTestFilePath); @@ -147,7 +147,7 @@ public void ExecutorInitializeWithValidSourceShouldAddItToTestSources() public void ExecutorExecuteForNoSourcesShouldReturnFail() { - var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager, null); Assert.ThrowsExactly(() => executor.Execute()); @@ -164,7 +164,7 @@ public void ExecutorExecuteShouldThrowTestPlatformException() ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager, null); Assert.ThrowsExactly(() => executor.Execute()); @@ -181,7 +181,7 @@ public void ExecutorExecuteShouldThrowSettingsException() ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var listTestsArgumentExecutor = GetExecutor(testRequestManager, null); Assert.ThrowsExactly(() => listTestsArgumentExecutor.Execute()); @@ -198,7 +198,7 @@ public void ExecutorExecuteShouldThrowInvalidOperationException() ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var listTestsArgumentExecutor = GetExecutor(testRequestManager, null); Assert.ThrowsExactly(() => listTestsArgumentExecutor.Execute()); @@ -215,7 +215,7 @@ public void ExecutorExecuteShouldThrowOtherExceptions() ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager, null); Assert.ThrowsExactly(() => executor.Execute()); @@ -274,7 +274,7 @@ private void RunListTestArgumentProcessorExecuteWithMockSetup(Mock(() => executor.Initialize(null)); @@ -131,7 +131,7 @@ public void InitializeShouldThrowIfArgumentIsNull() public void InitializeShouldThrowIfArgumentIsEmpty() { - var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Initialize(string.Empty)); @@ -141,7 +141,7 @@ public void InitializeShouldThrowIfArgumentIsEmpty() public void InitializeShouldThrowIfArgumentIsWhiteSpace() { - var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Initialize(" ")); @@ -151,7 +151,7 @@ public void InitializeShouldThrowIfArgumentIsWhiteSpace() public void InitializeShouldThrowIfArgumentsAreEmpty() { - var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Initialize(" , ")); @@ -161,7 +161,7 @@ public void InitializeShouldThrowIfArgumentsAreEmpty() public void ExecutorShouldSplitTestsSeparatedByComma() { - var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Execute()); @@ -171,7 +171,7 @@ public void ExecutorShouldSplitTestsSeparatedByComma() public void ExecutorExecuteForNoSourcesShouldThrowCommandLineException() { - var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Execute()); @@ -196,7 +196,7 @@ public void ExecutorExecuteForValidSourceWithTestCaseFilterShouldRunTests() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockTestRunRequest.Object); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); _commandLineOptions.TestCaseFilterValue = "Filter"; @@ -221,7 +221,7 @@ public void ExecutorExecuteShouldThrowTestPlatformExceptionThrownDuringDiscovery mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Execute()); @@ -239,7 +239,7 @@ public void ExecutorExecuteShouldThrowInvalidOperationExceptionThrownDuringDisco mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Execute()); @@ -257,7 +257,7 @@ public void ExecutorExecuteShouldThrowSettingsExceptionThrownDuringDiscovery() mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Execute()); @@ -282,7 +282,7 @@ public void ExecutorExecuteShouldThrowTestPlatformExceptionThrownDuringExecution mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -309,7 +309,7 @@ public void ExecutorExecuteShouldThrowSettingsExceptionThrownDuringExecution() mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -337,7 +337,7 @@ public void ExecutorExecuteShouldThrowInvalidOperationExceptionThrownDuringExecu ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -358,7 +358,7 @@ public void ExecutorExecuteShouldForValidSourcesAndNoTestsDiscoveredShouldLogWar mockDiscoveryRequest.Setup(dr => dr.DiscoverAsync()).Raises(dr => dr.OnDiscoveredTests += null, new DiscoveredTestsEventArgs(new List())); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -379,7 +379,7 @@ public void ExecutorExecuteShouldForValidSourcesAndNoTestsDiscoveredShouldLogApp mockDiscoveryRequest.Setup(dr => dr.DiscoverAsync()).Raises(dr => dr.OnDiscoveredTests += null, new DiscoveredTestsEventArgs(new List())); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -405,7 +405,7 @@ public void ExecutorExecuteShouldForValidSourcesAndValidSelectedTestsRunsTestsAn mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockTestRunRequest.Object); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -433,7 +433,7 @@ public void ExecutorShouldRunTestsWhenTestsAreCommaSeparated() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockTestRunRequest.Object); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1, Test2"); @@ -462,7 +462,7 @@ public void ExecutorShouldRunTestsWhenTestsAreFiltered() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockTestRunRequest.Object); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -487,7 +487,7 @@ public void ExecutorShouldWarnWhenTestsAreNotAvailable() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockTestRunRequest.Object); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1, Test2"); @@ -516,7 +516,7 @@ public void ExecutorShouldRunTestsWhenTestsAreCommaSeparatedWithEscape() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockTestRunRequest.Object); mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1(a\\,b), Test2(c\\,d)"); @@ -548,7 +548,7 @@ public void ExecutorShouldDisplayWarningIfNoTestsAreExecuted() mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); @@ -579,7 +579,7 @@ public void ExecutorShouldNotDisplayWarningIfTestsAreExecuted() mockTestPlatform.Setup(tp => tp.CreateDiscoveryRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockDiscoveryRequest.Object); ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _mockEnvironment.Object, _mockEnvironmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); executor.Initialize("Test1"); diff --git a/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs index ca6dacfcd7..377a867187 100644 --- a/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/RunTestsArgumentProcessorTests.cs @@ -120,7 +120,7 @@ public void ExecutorExecuteShouldReturnSuccessWithoutExecutionInDesignMode() runSettingsProvider.UpdateRunSettings(""); _commandLineOptions.IsDesignMode = true; - var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); var executor = new RunTestsArgumentExecutor(_commandLineOptions, runSettingsProvider, testRequestManager, _artifactProcessingManager.Object, _mockOutput.Object); Assert.AreEqual(ArgumentProcessorResult.Success, executor.Execute()); @@ -129,7 +129,7 @@ public void ExecutorExecuteShouldReturnSuccessWithoutExecutionInDesignMode() [TestMethod] public void ExecutorExecuteForNoSourcesShouldThrowCommandLineException() { - var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, TestPlatformFactory.GetTestPlatform(), new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Execute()); @@ -159,7 +159,7 @@ public void ExecutorExecuteShouldThrowTestPlatformException() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockTestRunRequest.Object); ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Execute()); @@ -175,7 +175,7 @@ public void ExecutorExecuteShouldThrowSettingsException() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockTestRunRequest.Object); ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Execute()); @@ -191,7 +191,7 @@ public void ExecutorExecuteShouldThrowInvalidOperationException() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockTestRunRequest.Object); ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Execute()); @@ -207,7 +207,7 @@ public void ExecutorExecuteShouldThrowOtherExceptions() mockTestPlatform.Setup(tp => tp.CreateTestRunRequest(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny())).Returns(mockTestRunRequest.Object); ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); Assert.ThrowsExactly(() => executor.Execute()); @@ -264,7 +264,7 @@ private ArgumentProcessorResult RunRunArgumentProcessorExecuteWithMockSetup(ITes ResetAndAddSourceToCommandLineOptions(); - var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, TestRunResultAggregator.Instance, _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); + var testRequestManager = new TestRequestManager(_commandLineOptions, mockTestPlatform.Object, new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, _mockProcessHelper.Object, _mockAttachmentsProcessingManager.Object, _environment.Object, _environmentVariableHelper.Object); var executor = GetExecutor(testRequestManager); return executor.Execute(); diff --git a/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs b/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs index 3a9cae7cf9..0581115e7f 100644 --- a/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs +++ b/test/vstest.console.UnitTests/TestPlatformHelpers/TestRequestManagerTests.cs @@ -130,7 +130,7 @@ public void TestRequestManagerShouldNotInitializeConsoleLoggerIfDesignModeIsSet( _mockLoggerEvents = new DummyLoggerEvents(TestSessionMessageLogger.Instance); _ = new TestRequestManager(_commandLineOptions, new Mock().Object, - TestRunResultAggregator.Instance, + new TestRunResultAggregator(), new Mock().Object, _inferHelper, _mockMetricsPublisherTask, @@ -212,7 +212,7 @@ public void DiscoverTestsShouldCallTestPlatformAndSucceed() _commandLineOptions.TestCaseFilterValue = testCaseFilterValue; _testRequestManager = new TestRequestManager(_commandLineOptions, _mockTestPlatform.Object, - TestRunResultAggregator.Instance, + new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, @@ -264,7 +264,7 @@ public void DiscoverTestsShouldPassSameProtocolConfigInRequestData() _commandLineOptions.TestCaseFilterValue = testCaseFilterValue; _testRequestManager = new TestRequestManager(_commandLineOptions, _mockTestPlatform.Object, - TestRunResultAggregator.Instance, + new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, @@ -314,7 +314,7 @@ public void DiscoverTestsShouldCollectMetrics() _testRequestManager = new TestRequestManager( _commandLineOptions, _mockTestPlatform.Object, - TestRunResultAggregator.Instance, + new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, @@ -365,7 +365,7 @@ public void DiscoverTestsShouldCollectTargetDeviceLocalMachineIfTargetDeviceStri _testRequestManager = new TestRequestManager( _commandLineOptions, _mockTestPlatform.Object, - TestRunResultAggregator.Instance, + new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, @@ -410,7 +410,7 @@ public void DiscoverTestsShouldCollectTargetDeviceIfTargetDeviceIsDevice() _testRequestManager = new TestRequestManager( _commandLineOptions, _mockTestPlatform.Object, - TestRunResultAggregator.Instance, + new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, @@ -455,7 +455,7 @@ public void DiscoverTestsShouldCollectTargetDeviceIfTargetDeviceIsEmulator() _testRequestManager = new TestRequestManager( _commandLineOptions, _mockTestPlatform.Object, - TestRunResultAggregator.Instance, + new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, @@ -500,7 +500,7 @@ public void DiscoverTestsShouldCollectCommands() _testRequestManager = new TestRequestManager( _commandLineOptions, _mockTestPlatform.Object, - TestRunResultAggregator.Instance, + new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, @@ -557,7 +557,7 @@ public void DiscoverTestsShouldCollectTestSettings() _testRequestManager = new TestRequestManager( _commandLineOptions, _mockTestPlatform.Object, - TestRunResultAggregator.Instance, + new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, @@ -605,7 +605,7 @@ public void DiscoverTestsShouldCollectVsmdiFile() _testRequestManager = new TestRequestManager( _commandLineOptions, _mockTestPlatform.Object, - TestRunResultAggregator.Instance, + new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, @@ -653,7 +653,7 @@ public void DiscoverTestsShouldCollectTestRunConfigFile() _testRequestManager = new TestRequestManager( _commandLineOptions, _mockTestPlatform.Object, - TestRunResultAggregator.Instance, + new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, @@ -957,7 +957,7 @@ public void RunTestsShouldCollectCommands() _testRequestManager = new TestRequestManager( _commandLineOptions, _mockTestPlatform.Object, - TestRunResultAggregator.Instance, + new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, @@ -1023,7 +1023,7 @@ public void RunTestsShouldCollectTelemetryForLegacySettings() _testRequestManager = new TestRequestManager( _commandLineOptions, _mockTestPlatform.Object, - TestRunResultAggregator.Instance, + new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, @@ -1072,7 +1072,7 @@ public void RunTestsShouldCollectTelemetryForTestSettingsEmbeddedInsideRunSettin _testRequestManager = new TestRequestManager( _commandLineOptions, _mockTestPlatform.Object, - TestRunResultAggregator.Instance, + new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, @@ -1119,7 +1119,7 @@ public void RunTestsShouldCollectMetrics() _testRequestManager = new TestRequestManager( _commandLineOptions, _mockTestPlatform.Object, - TestRunResultAggregator.Instance, + new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, @@ -1168,7 +1168,7 @@ public void RunTestsWithSourcesShouldCallTestPlatformAndSucceed() payload.TestPlatformOptions = new TestPlatformOptions { TestCaseFilter = testCaseFilterValue }; _testRequestManager = new TestRequestManager(_commandLineOptions, _mockTestPlatform.Object, - TestRunResultAggregator.Instance, + new TestRunResultAggregator(), _mockTestPlatformEventSource.Object, _inferHelper, _mockMetricsPublisherTask, From e5bf9e0b9f7f79854f376970030093477505a458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Mon, 13 Jul 2026 11:53:19 +0200 Subject: [PATCH 57/87] Remove non-functional enable-auto-merge workflow (#16267) The Enable auto merge workflow has failed on every Maestro dependency PR since it was added. It calls enablePullRequestAutoMerge with the default GITHUB_TOKEN, but main's branch protection restricts pushes to an empty allowlist, so github-actions[bot] is rejected with 'Pull request User is not authorized for this protected branch'. It is not a required check and provides no value in its current state, so remove it. Making auto-merge actually work would require a branch-protection settings change (allowlist a bot/App/PAT with merge rights), not this workflow. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/workflows/enable-auto-merge.yml | 26 ------------------------- 1 file changed, 26 deletions(-) delete mode 100644 .github/workflows/enable-auto-merge.yml diff --git a/.github/workflows/enable-auto-merge.yml b/.github/workflows/enable-auto-merge.yml deleted file mode 100644 index 69586339c1..0000000000 --- a/.github/workflows/enable-auto-merge.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Enable auto merge -on: - pull_request_target: - types: [opened, ready_for_review] -permissions: - contents: write - pull-requests: write -jobs: - add_milestone: - runs-on: ubuntu-latest - if: ${{ github.repository == 'microsoft/vstest' && github.event.pull_request.user.login == 'dotnet-maestro[bot]' && (startsWith(github.event.pull_request.title, '[main] Source code updates from dotnet/') || startsWith(github.event.pull_request.title, '[main] Update dependencies from dotnet/') || startsWith(github.event.pull_request.title, '[main] Update dependencies from devdiv/')) }} - steps: - - name: Enable pull request auto-merge - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PULL_REQUEST_ID: ${{ github.event.pull_request.node_id }} - run: | - gh api graphql -f query=' - mutation($pull: ID!) { - enablePullRequestAutoMerge(input: {pullRequestId: $pull, mergeMethod: SQUASH}) { - pullRequest { - id - number - } - } - }' -f pull=$PULL_REQUEST_ID From 4e51c3090c53280d2c97cf3260b3191269cab6b5 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 10:54:06 +0100 Subject: [PATCH 58/87] [main] Source code updates from dotnet/dotnet (#16266) * Backflow from https://github.com/dotnet/dotnet / 50dbab4 build 322464 Diff: https://github.com/dotnet/dotnet/compare/4a6c691f97cd93b08ef0794d297de622e4409e66..50dbab4de210e882172b07934e9666313b7065f1 From: https://github.com/dotnet/dotnet/commit/4a6c691f97cd93b08ef0794d297de622e4409e66 To: https://github.com/dotnet/dotnet/commit/50dbab4de210e882172b07934e9666313b7065f1 [[ commit created by automation ]] * Update dependencies from build 322464 Updated Dependencies: Microsoft.Diagnostics.NETCore.Client (Version 0.2.0-preview.26355.102 -> 0.2.0-preview.26360.111) [[ commit created by automation ]] --------- Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.props | 2 +- eng/Version.Details.xml | 6 +++--- eng/restore-internal-tools.yml | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/eng/Version.Details.props b/eng/Version.Details.props index bbe996e9f4..515736c67a 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -10,7 +10,7 @@ This file should be imported by eng/Versions.props 2.0.0 - 0.2.0-preview.26355.102 + 0.2.0-preview.26360.111 6.0.2 10.0.0 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 7692eabae4..c6faaa468b 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,14 +1,14 @@ - + https://dev.azure.com/devdiv/DevDiv/_git/vs-code-coverage 4f52ad52b601270ab25335be7889019de223e3c4 - + https://github.com/dotnet/dotnet - b4b350a66ea5dcf13420747036d8b263cdf6cbef + 50dbab4de210e882172b07934e9666313b7065f1 diff --git a/eng/restore-internal-tools.yml b/eng/restore-internal-tools.yml index 01cd835c5b..39f2f96fe2 100644 --- a/eng/restore-internal-tools.yml +++ b/eng/restore-internal-tools.yml @@ -1,7 +1,8 @@ steps: - task: NuGetAuthenticate@1 inputs: - nuGetServiceConnections: 'devdiv/dotnet-core-internal-tooling' + azureDevOpsServiceConnection: dnceng-devdiv-dotnet-core-internal-tooling-feed-rw-wif + feedUrl: https://pkgs.dev.azure.com/devdiv/_packaging/dotnet-core-internal-tooling/nuget/v3/index.json forceReinstallCredentialProvider: true - script: $(Build.SourcesDirectory)\eng\RestoreInternal.cmd From 1cd80231bc96d441f3959b56082887d0560429b7 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 10:54:11 +0100 Subject: [PATCH 59/87] Update dependencies from https://dev.azure.com/devdiv/DevDiv/_git/vs-code-coverage build 20260710.1 (#16265) On relative base path root Microsoft.Internal.CodeCoverage From Version 18.9.0-preview.26353.4 -> To Version 18.10.0-preview.26360.1 Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.props | 2 +- eng/Version.Details.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 515736c67a..2294858b86 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -18,7 +18,7 @@ This file should be imported by eng/Versions.props 1.1.0-beta2-19575-01 1.1.0-beta2-19575-01 - 18.9.0-preview.26353.4 + 18.10.0-preview.26360.1 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index c6faaa468b..5d39f98194 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -2,9 +2,9 @@ - + https://dev.azure.com/devdiv/DevDiv/_git/vs-code-coverage - 4f52ad52b601270ab25335be7889019de223e3c4 + c8afd6efa3de89dbdff966c28c6c6e7a53ec522b https://github.com/dotnet/dotnet From 282a1a6eb4bd9e53a5c747ddf5683909a2661204 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 10:54:19 +0100 Subject: [PATCH 60/87] Update dependencies from https://github.com/dotnet/arcade build 20260710.7 (#16264) On relative base path root Microsoft.DotNet.Arcade.Sdk From Version 11.0.0-beta.26330.1 -> To Version 11.0.0-beta.26360.7 Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.props | 2 +- eng/Version.Details.xml | 4 +- eng/common/SetupNugetSources.ps1 | 6 +- .../job/publish-build-assets.yml | 3 - .../core-templates/post-build/post-build.yml | 2 - eng/common/cross/build-rootfs.sh | 52 +++++++++++-- eng/common/dotnet-install.ps1 | 5 +- eng/common/dotnet-install.sh | 9 ++- eng/common/native/NativeAotSupported.props | 2 + eng/common/tools.ps1 | 75 ++++++++++--------- eng/common/tools.sh | 20 ++--- global.json | 2 +- 12 files changed, 117 insertions(+), 65 deletions(-) diff --git a/eng/Version.Details.props b/eng/Version.Details.props index 2294858b86..e661ebd543 100644 --- a/eng/Version.Details.props +++ b/eng/Version.Details.props @@ -6,7 +6,7 @@ This file should be imported by eng/Versions.props - 11.0.0-beta.26330.1 + 11.0.0-beta.26360.7 2.0.0 diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 5d39f98194..43c89b7571 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -26,9 +26,9 @@ - + https://github.com/dotnet/arcade - f87bce1e0d389d515282c5f74466d629ef653026 + 3169db01948537e61a9102477fab4a39663ba79d https://github.com/dotnet/symreader-converter diff --git a/eng/common/SetupNugetSources.ps1 b/eng/common/SetupNugetSources.ps1 index 58002808bc..b3bddff355 100644 --- a/eng/common/SetupNugetSources.ps1 +++ b/eng/common/SetupNugetSources.ps1 @@ -13,7 +13,11 @@ # filePath: $(System.DefaultWorkingDirectory)/eng/common/SetupNugetSources.ps1 # arguments: -ConfigFile $(System.DefaultWorkingDirectory)/NuGet.config -Password $Env:Token # env: -# Token: $(dn-bot-dnceng-artifact-feeds-rw) +# Token: $(InternalFeedToken) +# +# Note: This logic is abstracted into enable-internal-sources.yml, which uses +# NuGetAuthenticate or a WIF-backed service connection. Prefer that template +# over calling this script directly. # # Note that the NuGetAuthenticate task should be called after SetupNugetSources. # This ensures that: diff --git a/eng/common/core-templates/job/publish-build-assets.yml b/eng/common/core-templates/job/publish-build-assets.yml index 700f771146..4229288d3d 100644 --- a/eng/common/core-templates/job/publish-build-assets.yml +++ b/eng/common/core-templates/job/publish-build-assets.yml @@ -122,9 +122,6 @@ jobs: # Populate internal runtime variables. - template: /eng/common/templates/steps/enable-internal-sources.yml - ${{ if eq(variables['System.TeamProject'], 'DevDiv') }}: - parameters: - legacyCredential: $(dn-bot-dnceng-artifact-feeds-rw) - template: /eng/common/templates/steps/enable-internal-runtimes.yml diff --git a/eng/common/core-templates/post-build/post-build.yml b/eng/common/core-templates/post-build/post-build.yml index 8aa86e3049..9d95135269 100644 --- a/eng/common/core-templates/post-build/post-build.yml +++ b/eng/common/core-templates/post-build/post-build.yml @@ -295,8 +295,6 @@ stages: # Populate internal runtime variables. - template: /eng/common/templates/steps/enable-internal-sources.yml - parameters: - legacyCredential: $(dn-bot-dnceng-artifact-feeds-rw) - template: /eng/common/templates/steps/enable-internal-runtimes.yml diff --git a/eng/common/cross/build-rootfs.sh b/eng/common/cross/build-rootfs.sh index 3150ccac6f..453bb1ba5b 100644 --- a/eng/common/cross/build-rootfs.sh +++ b/eng/common/cross/build-rootfs.sh @@ -18,7 +18,10 @@ usage() echo "--skipsigcheck - optional, will skip package signature checks (allowing untrusted packages)." echo "--skipemulation - optional, will skip qemu and debootstrap requirement when building environment for debian based systems." echo "--use-mirror - optional, use mirror URL to fetch resources, when available." - echo "--jobs N - optional, restrict to N jobs." + echo "--ubuntu-repo - optional, override the Ubuntu apt repository base URL." + echo "--debian-repo - optional, override the Debian apt repository base URL." + echo "--alpine-repo - optional, override the Alpine Linux repository base URL." + echo "--jobs N (or --use-jobs N) - optional, restrict to N jobs." exit 1 } @@ -144,6 +147,9 @@ __KeyringFile="/usr/share/keyrings/ubuntu-archive-keyring.gpg" __SkipSigCheck=0 __SkipEmulation=0 __UseMirror=0 +__UbuntuRepoOverride= +__DebianRepoOverride= +__AlpineRepoOverride= __UnprocessedBuildArgs= while :; do @@ -397,6 +403,31 @@ while :; do --use-mirror) __UseMirror=1 ;; + --ubuntu-repo|-ubuntu-repo) + shift + if [[ "$#" -le 0 ]]; then + echo "ERROR: --ubuntu-repo requires a URL argument." + usage + fi + __UbuntuRepoOverride="$1" + ;; + --debian-repo|-debian-repo) + shift + if [[ "$#" -le 0 ]]; then + echo "ERROR: --debian-repo requires a URL argument." + usage + fi + __DebianRepoOverride="$1" + ;; + --alpine-repo|-alpine-repo) + shift + if [[ "$#" -le 0 ]]; then + echo "ERROR: --alpine-repo requires a URL argument." + usage + fi + __AlpineRepoOverride="$1" + ;; + # Removed duplicate/invalid option handling block (was breaking case statement parsing). --use-jobs) shift MAXJOBS=$1 @@ -446,6 +477,12 @@ if [[ -z "$__UbuntuRepo" ]]; then __UbuntuRepo="https://ports.ubuntu.com/" fi +if [[ -n "$__UbuntuRepoOverride" && "$__KeyringFile" == *ubuntu* ]]; then + __UbuntuRepo="$__UbuntuRepoOverride" +elif [[ -n "$__DebianRepoOverride" && "$__KeyringFile" == *debian* ]]; then + __UbuntuRepo="$__DebianRepoOverride" +fi + if [[ -n "$__LLVM_MajorVersion" ]]; then __UbuntuPackages+=" libclang-common-${__LLVM_MajorVersion}${__LLVM_MinorVersion:+.$__LLVM_MinorVersion}-dev" fi @@ -486,6 +523,7 @@ if [[ "$__CodeName" == "alpine" ]]; then __ApkToolsDir="$(mktemp -d)" __ApkKeysDir="$(mktemp -d)" arch="$(uname -m)" + __AlpineRepo="${__AlpineRepoOverride:-https://dl-cdn.alpinelinux.org/alpine}" ensureDownloadTool @@ -530,15 +568,15 @@ if [[ "$__CodeName" == "alpine" ]]; then # initialize DB # shellcheck disable=SC2086 "$__ApkToolsDir/apk.static" \ - -X "https://dl-cdn.alpinelinux.org/alpine/$version/main" \ - -X "https://dl-cdn.alpinelinux.org/alpine/$version/community" \ + -X "$__AlpineRepo/$version/main" \ + -X "$__AlpineRepo/$version/community" \ -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" --initdb add if [[ "$__AlpineLlvmLibsLookup" == 1 ]]; then # shellcheck disable=SC2086 __AlpinePackages+=" $("$__ApkToolsDir/apk.static" \ - -X "https://dl-cdn.alpinelinux.org/alpine/$version/main" \ - -X "https://dl-cdn.alpinelinux.org/alpine/$version/community" \ + -X "$__AlpineRepo/$version/main" \ + -X "$__AlpineRepo/$version/community" \ -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" \ search 'llvm*-libs' | grep -E '^llvm' | sort | tail -1 | sed 's/-[^-]*//2g')" fi @@ -546,8 +584,8 @@ if [[ "$__CodeName" == "alpine" ]]; then # install all packages in one go # shellcheck disable=SC2086 "$__ApkToolsDir/apk.static" \ - -X "https://dl-cdn.alpinelinux.org/alpine/$version/main" \ - -X "https://dl-cdn.alpinelinux.org/alpine/$version/community" \ + -X "$__AlpineRepo/$version/main" \ + -X "$__AlpineRepo/$version/community" \ -U $__ApkSignatureArg --root "$__RootfsDir" --arch "$__AlpineArch" $__NoEmulationArg \ add $__AlpinePackages diff --git a/eng/common/dotnet-install.ps1 b/eng/common/dotnet-install.ps1 index 50ae627376..b6d45f2bdc 100644 --- a/eng/common/dotnet-install.ps1 +++ b/eng/common/dotnet-install.ps1 @@ -4,13 +4,16 @@ Param( [string] $architecture = '', [string] $version = 'Latest', [string] $runtime = 'dotnet', + [string] $dotnetPath = '', [string] $RuntimeSourceFeed = '', [string] $RuntimeSourceFeedKey = '' ) . $PSScriptRoot\tools.ps1 -if (-not [string]::IsNullOrEmpty($env:DOTNET_GLOBAL_INSTALL_DIR)) { +if (-not [string]::IsNullOrEmpty($dotnetPath)) { + $dotnetRoot = $dotnetPath +} elseif (-not [string]::IsNullOrEmpty($env:DOTNET_GLOBAL_INSTALL_DIR)) { $dotnetRoot = $env:DOTNET_GLOBAL_INSTALL_DIR } else { $dotnetRoot = Join-Path $RepoRoot '.dotnet' diff --git a/eng/common/dotnet-install.sh b/eng/common/dotnet-install.sh index 1cb3f5abac..58a7e6f384 100755 --- a/eng/common/dotnet-install.sh +++ b/eng/common/dotnet-install.sh @@ -16,6 +16,7 @@ scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" version='Latest' architecture='' runtime='dotnet' +dotnetPath='' runtimeSourceFeed='' runtimeSourceFeedKey='' while [[ $# -gt 0 ]]; do @@ -33,6 +34,10 @@ while [[ $# -gt 0 ]]; do shift runtime="$1" ;; + -dotnetpath) + shift + dotnetPath="$1" + ;; -runtimesourcefeed) shift runtimeSourceFeed="$1" @@ -80,7 +85,9 @@ case $cpuname in ;; esac -if [[ -n "${DOTNET_GLOBAL_INSTALL_DIR:-}" ]]; then +if [[ -n "${dotnetPath:-}" ]]; then + dotnetRoot="$dotnetPath" +elif [[ -n "${DOTNET_GLOBAL_INSTALL_DIR:-}" ]]; then dotnetRoot="$DOTNET_GLOBAL_INSTALL_DIR" else dotnetRoot="${repo_root}.dotnet" diff --git a/eng/common/native/NativeAotSupported.props b/eng/common/native/NativeAotSupported.props index 559a666392..cdff9ef036 100644 --- a/eng/common/native/NativeAotSupported.props +++ b/eng/common/native/NativeAotSupported.props @@ -13,6 +13,8 @@ <_NativeAotSupportedArch Condition=" '$(TargetArchitecture)' != 'wasm' and + '$(TargetArchitecture)' != 's390x' and + '$(TargetArchitecture)' != 'ppc64le' and ('$(TargetArchitecture)' != 'x86' or '$(TargetOS)' == 'windows') ">true diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index de32a6da37..6f664ad890 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -432,11 +432,31 @@ function InitializeVisualStudioMSBuild([object]$vsRequirements = $null) { $msbuildVersionDir = if ([int]$vsMajorVersion -lt 16) { "$vsMajorVersion.0" } else { "Current" } $local:BinFolder = Join-Path $vsInstallDir "MSBuild\$msbuildVersionDir\Bin" - $local:Prefer64bit = if (Get-Member -InputObject $vsRequirements -Name 'Prefer64bit') { $vsRequirements.Prefer64bit } else { $false } - if ($local:Prefer64bit -and (Test-Path(Join-Path $local:BinFolder "amd64"))) { - $global:_MSBuildExe = Join-Path $local:BinFolder "amd64\msbuild.exe" - } else { - $global:_MSBuildExe = Join-Path $local:BinFolder "msbuild.exe" + + # Use the MSBuild matching the host's process architecture (e.g. amd64 or arm64), + # falling back to the 32-bit MSBuild in the root Bin folder when no matching subfolder exists. + + # Determine the architecture of the current process, accounting for a 32-bit process + # running on a 64-bit OS (PROCESSOR_ARCHITEW6432 holds the real machine architecture). + $local:ProcessArchitecture = $env:PROCESSOR_ARCHITECTURE + if (($local:ProcessArchitecture -eq 'x86') -and ($env:PROCESSOR_ARCHITEW6432)) { + $local:ProcessArchitecture = $env:PROCESSOR_ARCHITEW6432 + } + + # Map the architecture to the corresponding MSBuild subfolder. The 32-bit MSBuild lives in the + # root Bin folder, so x86 maps to an empty subfolder. + $local:MSBuildArchSubFolder = switch ($local:ProcessArchitecture) { + 'AMD64' { 'amd64' } + 'ARM64' { 'arm64' } + default { '' } + } + + $global:_MSBuildExe = Join-Path $local:BinFolder "msbuild.exe" + if ($local:MSBuildArchSubFolder) { + $local:ArchMSBuildExe = Join-Path $local:BinFolder (Join-Path $local:MSBuildArchSubFolder "msbuild.exe") + if (Test-Path $local:ArchMSBuildExe) { + $global:_MSBuildExe = $local:ArchMSBuildExe + } } return $global:_MSBuildExe @@ -531,6 +551,16 @@ function LocateVisualStudio([object]$vsRequirements = $null){ } function InitializeBuildTool() { + # Allow a caller (e.g. a bootstrap script running out-of-proc) to inject the build tool via + # environment variables instead of the in-proc $global:_BuildTool variable. Only Path and + # Command are consumed by the MSBuild function below, so those are all that's needed. + if ($env:_BuildToolPath) { + return $global:_BuildTool = @{ + Path = $env:_BuildToolPath + Command = $env:_BuildToolCommand + } + } + if (Test-Path variable:global:_BuildTool) { # If the requested msbuild parameters do not match, clear the cached variables. if($global:_BuildTool.Contains('ExcludePrereleaseVS') -and $global:_BuildTool.ExcludePrereleaseVS -ne $excludePrereleaseVS) { @@ -558,7 +588,7 @@ function InitializeBuildTool() { } $dotnetPath = Join-Path $dotnetRoot (GetExecutableFileName 'dotnet') - $buildTool = @{ Path = $dotnetPath; Command = 'msbuild'; Tool = 'dotnet'; Framework = 'net' } + $buildTool = @{ Path = $dotnetPath; Command = 'msbuild' } } elseif ($msbuildEngine -eq "vs") { try { $msbuildPath = InitializeVisualStudioMSBuild @@ -567,7 +597,7 @@ function InitializeBuildTool() { ExitWithExitCode 1 } - $buildTool = @{ Path = $msbuildPath; Command = ""; Tool = "vs"; Framework = "netframework"; ExcludePrereleaseVS = $excludePrereleaseVS } + $buildTool = @{ Path = $msbuildPath; Command = ""; ExcludePrereleaseVS = $excludePrereleaseVS } } else { Write-PipelineTelemetryError -Category 'InitializeToolset' -Message "Unexpected value of -msbuildEngine: '$msbuildEngine'." ExitWithExitCode 1 @@ -706,7 +736,7 @@ function InitializeToolset() { } function ExitWithExitCode([int] $exitCode) { - if ($ci -and $prepareMachine) { + if ($prepareMachine) { Stop-Processes } exit $exitCode @@ -741,13 +771,6 @@ function MSBuild() { Write-PipelineTelemetryError -Category 'Build' -Message 'Binary log must be enabled in CI build, or explicitly opted-out from with the -excludeCIBinarylog switch.' ExitWithExitCode 1 } - - # Node reuse must be disabled in CI builds unless explicitly opted in via MSBUILD_NODEREUSE_ENABLED. - # Internal testing only; this env var will be replaced with a switch (https://github.com/dotnet/arcade/issues/17013) and must not be depended on. - if ($nodeReuse -and $env:MSBUILD_NODEREUSE_ENABLED -ne "1") { - Write-PipelineTelemetryError -Category 'Build' -Message 'Node reuse must be disabled in CI build.' - ExitWithExitCode 1 - } } $buildTool = InitializeBuildTool @@ -789,11 +812,6 @@ function MSBuild() { # The build already logged an error, that's the reason it failed. Producing an error here only adds noise. Write-Host "Build failed with exit code $exitCode. Check errors above." -ForegroundColor Red - $buildLog = GetMSBuildBinaryLogCommandLineArgument $args - if ($null -ne $buildLog) { - Write-Host "See log: $buildLog" -ForegroundColor DarkGray - } - # When running on Azure Pipelines, override the returned exit code to avoid double logging. # Skip this when the build is a child of the VMR build. if ($ci -and $env:SYSTEM_TEAMPROJECT -ne $null -and !$fromVMR) { @@ -841,23 +859,6 @@ function DotNet() { } } -function GetMSBuildBinaryLogCommandLineArgument($arguments) { - foreach ($argument in $arguments) { - if ($argument -ne $null) { - $arg = $argument.Trim() - if ($arg.StartsWith('/bl:', "OrdinalIgnoreCase")) { - return $arg.Substring('/bl:'.Length) - } - - if ($arg.StartsWith('/binaryLogger:', 'OrdinalIgnoreCase')) { - return $arg.Substring('/binaryLogger:'.Length) - } - } - } - - return $null -} - function GetExecutableFileName($baseName) { if (IsWindowsPlatform) { return "$baseName.exe" diff --git a/eng/common/tools.sh b/eng/common/tools.sh index 3164fff333..347a29b888 100755 --- a/eng/common/tools.sh +++ b/eng/common/tools.sh @@ -353,6 +353,15 @@ function GetDotNetInstallScript { } function InitializeBuildTool { + # Allow a caller (e.g. a bootstrap script running out-of-proc) to inject the build tool via + # environment variables instead of the in-proc _InitializeBuildTool variable. Only the tool path and + # command are consumed by the MSBuild function below, so those are all that's needed. + if [[ -n "${_BuildToolPath:-}" ]]; then + _InitializeBuildTool="$_BuildToolPath" + _InitializeBuildToolCommand="$_BuildToolCommand" + return + fi + if [[ -n "${_InitializeBuildTool:-}" ]]; then return fi @@ -457,7 +466,7 @@ function InitializeToolset { } function ExitWithExitCode { - if [[ "$ci" == true && "$prepare_machine" == true ]]; then + if [[ "$prepare_machine" == true ]]; then StopProcesses fi exit $1 @@ -494,14 +503,7 @@ function DotNet { function MSBuild { if [[ "$ci" == true ]]; then if [[ "$binary_log" != true && "$exclude_ci_binary_log" != true ]]; then - Write-PipelineTelemetryError -category 'Build' "Binary log must be enabled in CI build, or explicitly opted-out from with the -noBinaryLog switch." - ExitWithExitCode 1 - fi - - # Node reuse must be disabled in CI builds unless explicitly opted in via MSBUILD_NODEREUSE_ENABLED. - # Internal testing only; this env var will be replaced with a switch (https://github.com/dotnet/arcade/issues/17013) and must not be depended on. - if [[ "$node_reuse" == true && "${MSBUILD_NODEREUSE_ENABLED:-}" != "1" ]]; then - Write-PipelineTelemetryError -category 'Build' "Node reuse must be disabled in CI build." + Write-PipelineTelemetryError -category 'Build' "Binary log must be enabled in CI build, or explicitly opted-out from with the --excludeCIBinarylog switch." ExitWithExitCode 1 fi fi diff --git a/global.json b/global.json index 8cc2cafe04..bea51dfbbe 100644 --- a/global.json +++ b/global.json @@ -17,7 +17,7 @@ "dotnet": "11.0.100-preview.5.26302.115" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "11.0.0-beta.26330.1" + "Microsoft.DotNet.Arcade.Sdk": "11.0.0-beta.26360.7" }, "test": { "runner": "Microsoft.Testing.Platform" From 446c3a12662a53c7afe4b4b968ec9d8b40c1d813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Mon, 13 Jul 2026 11:56:18 +0200 Subject: [PATCH 61/87] Load inbox extensions without reading the RunSettingsManager singleton (#16263) TestPlatform's static constructor read RunSettingsManager.Instance to pick the adapter loading strategy when it loads the inbox extensions. That read runs once at type load, before any request's run settings are known, so it only ever saw the default settings. Load with the default strategy directly instead, so this no longer depends on the shared RunSettingsManager singleton. The load stays in the static constructor because it has to run before host provider resolution reads DefaultExtensionPaths. Per request test adapter paths are still loaded through the normal PopulateExtensions flow. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/Microsoft.TestPlatform.Client/TestPlatform.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.TestPlatform.Client/TestPlatform.cs b/src/Microsoft.TestPlatform.Client/TestPlatform.cs index e6249bc52a..74ee59f5e2 100644 --- a/src/Microsoft.TestPlatform.Client/TestPlatform.cs +++ b/src/Microsoft.TestPlatform.Client/TestPlatform.cs @@ -246,7 +246,13 @@ private static void AddExtensionAssembliesFromExtensionDirectory() // Otherwise we will always get a "No suitable test runtime provider found for this run." error. // I (@haplois) will modify this behavior later on, but we also need to consider legacy adapters // and make sure they still work after modification. - string? runSettings = RunSettingsManager.Instance.ActiveRunSettings.SettingsXml; + // + // The inbox extensions are loaded once, at type initialization, using the default adapter + // loading strategy. We intentionally do not read the ambient RunSettingsManager singleton + // here: this runs before any request's run settings are known (see the note above), so it + // could only ever observe the default settings anyway. Request-specific test adapter paths + // are still loaded later, per request, through the normal PopulateExtensions flow. + string? runSettings = null; RunConfiguration runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(runSettings); TestAdapterLoadingStrategy strategy = runConfiguration.TestAdapterLoadingStrategy; From 59549971d7d902d23029985de696d8d80971e8f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Mon, 13 Jul 2026 13:40:33 +0200 Subject: [PATCH 62/87] Delete the RunSettingsManager.Instance singleton (#16268) The active run settings were shared process-wide through the RunSettingsManager.Instance singleton. In design mode one vstest.console process serves many requests, so that shared instance leaked run settings across them and forced tests to null and reset the static to isolate cases. #16263 removed the last production reader (the TestPlatform static constructor); the only references left were composition-root defaults. The Executor convenience constructor and the ArgumentProcessorFactory fallback now build a fresh, request-scoped new RunSettingsManager() instead of reading the singleton. RunSettingsManager is internal and not in the public API, so nothing outside the assembly could read it, and deleting Instance (with its backing field, lock, and setter) is safe. The tests move to a per-class new RunSettingsManager() field, so each test method gets its own instance and the save/restore and reset hygiene goes away. The two tests that only covered the singleton getter caching are removed with it. Validation: Release build clean; vstest.console.UnitTests 631/633 (2 pre-existing skips) and Common.UnitTests 393/395 pass on net11.0 and net481; smoke Acceptance 5/5 and Library 4/4 on both TFMs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../RunSettingsManager.cs | 30 +-------- src/vstest.console/CommandLine/Executor.cs | 2 +- .../Utilities/ArgumentProcessorFactory.cs | 6 +- .../RunSettingsManagerTests.cs | 31 +--------- .../ExecutorUnitTests.cs | 17 ++--- .../EnableLoggersArgumentProcessorTests.cs | 62 +++++++++---------- ...erLoadingStrategyArgumentProcessorTests.cs | 28 +++------ .../TestAdapterPathArgumentProcessorTests.cs | 32 +++------- 8 files changed, 60 insertions(+), 148 deletions(-) diff --git a/src/Microsoft.TestPlatform.Common/RunSettingsManager.cs b/src/Microsoft.TestPlatform.Common/RunSettingsManager.cs index f7cdcf6872..eafb5f4ba7 100644 --- a/src/Microsoft.TestPlatform.Common/RunSettingsManager.cs +++ b/src/Microsoft.TestPlatform.Common/RunSettingsManager.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Diagnostics.CodeAnalysis; using Microsoft.VisualStudio.TestPlatform.Common.Interfaces; @@ -13,14 +12,10 @@ namespace Microsoft.VisualStudio.TestPlatform.Common; /// internal class RunSettingsManager : IRunSettingsProvider { - private static readonly object LockObject = new(); - - private static RunSettingsManager? s_runSettingsManagerInstance; - /// /// Default constructor. /// - private RunSettingsManager() + internal RunSettingsManager() { ActiveRunSettings = new RunSettings(); } @@ -34,29 +29,6 @@ private RunSettingsManager() #endregion - [AllowNull] - public static RunSettingsManager Instance - { - get - { - if (s_runSettingsManagerInstance != null) - { - return s_runSettingsManagerInstance; - } - - lock (LockObject) - { - s_runSettingsManagerInstance ??= new RunSettingsManager(); - } - - return s_runSettingsManagerInstance; - } - internal set - { - s_runSettingsManagerInstance = value; - } - } - /// /// Set the active run settings. /// diff --git a/src/vstest.console/CommandLine/Executor.cs b/src/vstest.console/CommandLine/Executor.cs index 3ce196ecd6..9da5f3b401 100644 --- a/src/vstest.console/CommandLine/Executor.cs +++ b/src/vstest.console/CommandLine/Executor.cs @@ -101,7 +101,7 @@ internal class Executor } internal Executor(IOutput output, ITestPlatformEventSource testPlatformEventSource, IProcessHelper processHelper, IEnvironment environment) - : this(output, testPlatformEventSource, processHelper, environment, RunSettingsManager.Instance, RunSettingsHelper.Instance, new CommandLineOptions(), new TestRunResultAggregator()) + : this(output, testPlatformEventSource, processHelper, environment, new RunSettingsManager(), RunSettingsHelper.Instance, new CommandLineOptions(), new TestRunResultAggregator()) { } diff --git a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs index 05551606a5..c48bf60240 100644 --- a/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs +++ b/src/vstest.console/Processors/Utilities/ArgumentProcessorFactory.cs @@ -52,8 +52,8 @@ protected ArgumentProcessorFactory(IEnumerable argumentProce /// /// /// The run settings provider that the created argument processors read from and write to. - /// Defaults to the ambient when not provided, so that - /// callers (and the composition root) can inject an isolated instance instead of sharing static state. + /// When not provided a fresh, request-scoped instance is created, so that callers never + /// share run settings state through a static singleton. /// /// /// The run settings helper that the created argument processors write request-scoped flags to. @@ -74,7 +74,7 @@ protected ArgumentProcessorFactory(IEnumerable argumentProce /// ArgumentProcessorFactory. internal static ArgumentProcessorFactory Create(IFeatureFlag? featureFlag = null, IRunSettingsProvider? runSettingsProvider = null, IRunSettingsHelper? runSettingsHelper = null, CommandLineOptions? commandLineOptions = null, ITestRequestManager? testRequestManager = null) { - runSettingsProvider ??= RunSettingsManager.Instance; + runSettingsProvider ??= new RunSettingsManager(); runSettingsHelper ??= RunSettingsHelper.Instance; commandLineOptions ??= new CommandLineOptions(); testRequestManager ??= new LazyTestRequestManager(() => new TestRequestManager(commandLineOptions, new TestRunResultAggregator())); diff --git a/test/Microsoft.TestPlatform.Common.UnitTests/RunSettingsManagerTests.cs b/test/Microsoft.TestPlatform.Common.UnitTests/RunSettingsManagerTests.cs index a548d64cf4..29de56b5db 100644 --- a/test/Microsoft.TestPlatform.Common.UnitTests/RunSettingsManagerTests.cs +++ b/test/Microsoft.TestPlatform.Common.UnitTests/RunSettingsManagerTests.cs @@ -9,37 +9,12 @@ namespace TestPlatform.Common.UnitTests; [TestClass] -[DoNotParallelize] public class RunSettingsManagerTests { - [TestCleanup] - public void TestCleanup() - { - RunSettingsManager.Instance = null; - } - - [TestMethod] - public void InstanceShouldReturnARunSettingsManagerInstance() - { - var instance = RunSettingsManager.Instance; - - Assert.IsNotNull(instance); - Assert.AreEqual(typeof(RunSettingsManager), instance.GetType()); - } - - [TestMethod] - public void InstanceShouldReturnACachedValue() - { - var instance = RunSettingsManager.Instance; - var instance2 = RunSettingsManager.Instance; - - Assert.AreEqual(instance, instance2); - } - [TestMethod] public void ActiveRunSettingsShouldBeNonNullByDefault() { - var instance = RunSettingsManager.Instance; + var instance = new RunSettingsManager(); Assert.IsNotNull(instance.ActiveRunSettings); } @@ -47,7 +22,7 @@ public void ActiveRunSettingsShouldBeNonNullByDefault() [TestMethod] public void SetActiveRunSettingsShouldThrowIfRunSettingsPassedIsNull() { - var instance = RunSettingsManager.Instance; + var instance = new RunSettingsManager(); Assert.ThrowsExactly(() => instance.SetActiveRunSettings(null!)); } @@ -55,7 +30,7 @@ public void SetActiveRunSettingsShouldThrowIfRunSettingsPassedIsNull() [TestMethod] public void SetActiveRunSettingsShouldSetTheActiveRunSettingsProperty() { - var instance = RunSettingsManager.Instance; + var instance = new RunSettingsManager(); var runSettings = new RunSettings(); runSettings.LoadSettingsXml(""); diff --git a/test/vstest.console.UnitTests/ExecutorUnitTests.cs b/test/vstest.console.UnitTests/ExecutorUnitTests.cs index 061ee364f6..9b67074fbd 100644 --- a/test/vstest.console.UnitTests/ExecutorUnitTests.cs +++ b/test/vstest.console.UnitTests/ExecutorUnitTests.cs @@ -27,11 +27,12 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests; [TestClass] -// Because runsettings tests use the instance of RunSettingsManager which is static. +// These tests construct real Executors and share fixed temp-file names, so they must not run in parallel. [DoNotParallelize] public class ExecutorUnitTests { private readonly CommandLineOptions _commandLineOptions = new(); + private readonly RunSettingsManager _runSettingsManager = new(); private readonly Mock _mockTestPlatformEventSource; public ExecutorUnitTests() @@ -150,8 +151,8 @@ public void ExecutorWithInvalidArgsAndValueShouldPrintErrorMessage() public void ExecuteShouldInitializeDefaultRunsettings() { var mockOutput = new MockOutput(); - _ = new Executor(mockOutput, _mockTestPlatformEventSource.Object, new ProcessHelper(), new PlatformEnvironment()).Execute(null); - RunConfiguration runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(RunSettingsManager.Instance.ActiveRunSettings.SettingsXml); + _ = new Executor(mockOutput, _mockTestPlatformEventSource.Object, new ProcessHelper(), new PlatformEnvironment(), _runSettingsManager).Execute(null); + RunConfiguration runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(_runSettingsManager.ActiveRunSettings.SettingsXml); Assert.AreEqual(Constants.DefaultResultsDirectory, runConfiguration.ResultsDirectory); Assert.AreEqual(Framework.DefaultFramework.ToString(), runConfiguration.TargetFramework!.ToString()); Assert.AreEqual(Constants.DefaultPlatform, runConfiguration.TargetPlatform); @@ -212,7 +213,6 @@ public void ExecutorShouldPrintDotnetVSTestDeprecationMessage(string commandLine [TestMethod] public void ExecuteShouldNotThrowSettingsExceptionButLogOutput() { - var activeRunSetting = RunSettingsManager.Instance.ActiveRunSettings; var runSettingsFile = Path.Combine(Path.GetTempPath(), "ExecutorShouldShowRightErrorMessage.runsettings"); try @@ -244,14 +244,12 @@ public void ExecuteShouldNotThrowSettingsExceptionButLogOutput() finally { File.Delete(runSettingsFile); - RunSettingsManager.Instance.SetActiveRunSettings(activeRunSetting); } } [TestMethod] public void ExecuteShouldReturnNonZeroExitCodeIfSettingsException() { - var activeRunSetting = RunSettingsManager.Instance.ActiveRunSettings; var runSettingsFile = Path.Combine(Path.GetTempPath(), "ExecutorShouldShowRightErrorMessage.runsettings"); try @@ -281,14 +279,12 @@ public void ExecuteShouldReturnNonZeroExitCodeIfSettingsException() finally { File.Delete(runSettingsFile); - RunSettingsManager.Instance.SetActiveRunSettings(activeRunSetting); } } [TestMethod] public void ExecutorShouldShowRightErrorMessage() { - var activeRunSetting = RunSettingsManager.Instance.ActiveRunSettings; var runSettingsFile = Path.Combine(Path.GetTempPath(), "ExecutorShouldShowRightErrorMessage.runsettings"); try @@ -318,7 +314,6 @@ public void ExecutorShouldShowRightErrorMessage() finally { File.Delete(runSettingsFile); - RunSettingsManager.Instance.SetActiveRunSettings(activeRunSetting); } } @@ -384,7 +379,7 @@ public void MarkingTestRunFailedOnInjectedAggregatorIsObservedByExecutorExitCode _mockTestPlatformEventSource.Object, new ProcessHelper(), new PlatformEnvironment(), - RunSettingsManager.Instance, + _runSettingsManager, RunSettingsHelper.Instance, _commandLineOptions, injectedAggregator).Execute("--help"); @@ -399,7 +394,7 @@ public void MarkingTestRunFailedOnInjectedAggregatorIsObservedByExecutorExitCode _mockTestPlatformEventSource.Object, new ProcessHelper(), new PlatformEnvironment(), - RunSettingsManager.Instance, + _runSettingsManager, RunSettingsHelper.Instance, _commandLineOptions, defaultAggregator).Execute("--help"); diff --git a/test/vstest.console.UnitTests/Processors/EnableLoggersArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/EnableLoggersArgumentProcessorTests.cs index 1de1424ac6..5d106d32c2 100644 --- a/test/vstest.console.UnitTests/Processors/EnableLoggersArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/EnableLoggersArgumentProcessorTests.cs @@ -15,23 +15,17 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] -// Because runsettings tests use the instance of RunSettingsManager which is static. [DoNotParallelize] public class EnableLoggersArgumentProcessorTests { + private readonly RunSettingsManager _runSettingsManager = new(); + [TestInitialize] public void Initialize() { - RunSettingsManager.Instance = null; RunTestsArgumentProcessorTests.SetupMockExtensions(); } - [TestCleanup] - public void Cleanup() - { - RunSettingsManager.Instance = null; - } - [TestMethod] public void GetMetadataShouldReturnEnableLoggerArgumentProcessorCapabilities() { @@ -72,7 +66,7 @@ public void CapabilitiesShouldAppropriateProperties() [DataRow("TestLoggerExtension;==;;;Collection=http://localhost:8080/tfs/DefaultCollection;TeamProject=MyProject;BuildName=DailyBuild_20121130.1")] public void ExectorInitializeShouldThrowExceptionIfInvalidArgumentIsPassed(string argument) { - var executor = new EnableLoggerArgumentExecutor(RunSettingsManager.Instance); + var executor = new EnableLoggerArgumentExecutor(_runSettingsManager); var e = Assert.ThrowsExactly(() => executor.Initialize(argument)); string exceptionMessage = string.Format(CultureInfo.CurrentCulture, CommandLineResources.LoggerUriInvalid, argument); Assert.IsInstanceOfType(e); @@ -82,7 +76,7 @@ public void ExectorInitializeShouldThrowExceptionIfInvalidArgumentIsPassed(strin [TestMethod] public void ExecutorExecuteShouldReturnArgumentProcessorResultSuccess() { - var executor = new EnableLoggerArgumentExecutor(RunSettingsManager.Instance); + var executor = new EnableLoggerArgumentExecutor(_runSettingsManager); var result = executor.Execute(); Assert.AreEqual(ArgumentProcessorResult.Success, result); } @@ -105,9 +99,9 @@ public void ExecutorInitializeShouldAddLoggerWithFriendlyNameInRunSettingsIfName var runSettings = new RunSettings(); runSettings.LoadSettingsXml(settingsXml); - RunSettingsManager.Instance.SetActiveRunSettings(runSettings); + _runSettingsManager.SetActiveRunSettings(runSettings); - var executor = new EnableLoggerArgumentExecutor(RunSettingsManager.Instance); + var executor = new EnableLoggerArgumentExecutor(_runSettingsManager); executor.Initialize("DummyLoggerExtension"); string expectedSettingsXml = @@ -128,7 +122,7 @@ public void ExecutorInitializeShouldAddLoggerWithFriendlyNameInRunSettingsIfName "; - Assert.AreEqual(expectedSettingsXml, RunSettingsManager.Instance.ActiveRunSettings?.SettingsXml); + Assert.AreEqual(expectedSettingsXml, _runSettingsManager.ActiveRunSettings?.SettingsXml); } [TestMethod] @@ -149,9 +143,9 @@ public void ExecutorInitializeShouldAddLoggerWithUriInRunSettingsIfUriPresentInA var runSettings = new RunSettings(); runSettings.LoadSettingsXml(settingsXml); - RunSettingsManager.Instance.SetActiveRunSettings(runSettings); + _runSettingsManager.SetActiveRunSettings(runSettings); - var executor = new EnableLoggerArgumentExecutor(RunSettingsManager.Instance); + var executor = new EnableLoggerArgumentExecutor(_runSettingsManager); executor.Initialize("logger://DummyLoggerUri"); string expectedSettingsXml = @@ -172,7 +166,7 @@ public void ExecutorInitializeShouldAddLoggerWithUriInRunSettingsIfUriPresentInA "; - Assert.AreEqual(expectedSettingsXml, RunSettingsManager.Instance.ActiveRunSettings?.SettingsXml); + Assert.AreEqual(expectedSettingsXml, _runSettingsManager.ActiveRunSettings?.SettingsXml); } [TestMethod] @@ -193,9 +187,9 @@ public void ExecutorInitializeShouldCorrectlyAddLoggerParametersInRunSettings() var runSettings = new RunSettings(); runSettings.LoadSettingsXml(settingsXml); - RunSettingsManager.Instance.SetActiveRunSettings(runSettings); + _runSettingsManager.SetActiveRunSettings(runSettings); - var executor = new EnableLoggerArgumentExecutor(RunSettingsManager.Instance); + var executor = new EnableLoggerArgumentExecutor(_runSettingsManager); executor.Initialize("logger://DummyLoggerUri;Collection=http://localhost:8080/tfs/DefaultCollection;TeamProject=MyProject;BuildName=DailyBuild_20121130.1"); string expectedSettingsXml = @@ -222,15 +216,15 @@ public void ExecutorInitializeShouldCorrectlyAddLoggerParametersInRunSettings() "; - Assert.AreEqual(expectedSettingsXml, RunSettingsManager.Instance.ActiveRunSettings?.SettingsXml); + Assert.AreEqual(expectedSettingsXml, _runSettingsManager.ActiveRunSettings?.SettingsXml); } [TestMethod] public void ExecutorInitializeShouldCorrectlyAddLoggerWhenRunSettingsNotPassed() { - RunSettingsManager.Instance.SetActiveRunSettings(new RunSettings()); + _runSettingsManager.SetActiveRunSettings(new RunSettings()); - var executor = new EnableLoggerArgumentExecutor(RunSettingsManager.Instance); + var executor = new EnableLoggerArgumentExecutor(_runSettingsManager); executor.Initialize("logger://DummyLoggerUri;Collection=http://localhost:8080/tfs/DefaultCollection;TeamProject=MyProject;BuildName=DailyBuild_20121130.1"); string expectedSettingsXml = @@ -246,7 +240,7 @@ public void ExecutorInitializeShouldCorrectlyAddLoggerWhenRunSettingsNotPassed() "; - Assert.Contains(expectedSettingsXml, RunSettingsManager.Instance.ActiveRunSettings!.SettingsXml!); + Assert.Contains(expectedSettingsXml, _runSettingsManager.ActiveRunSettings!.SettingsXml!); } [TestMethod] @@ -281,9 +275,9 @@ public void ExecutorInitializeShouldCorrectlyAddLoggerInRunSettingsWhenOtherLogg var runSettings = new RunSettings(); runSettings.LoadSettingsXml(settingsXml); - RunSettingsManager.Instance.SetActiveRunSettings(runSettings); + _runSettingsManager.SetActiveRunSettings(runSettings); - var executor = new EnableLoggerArgumentExecutor(RunSettingsManager.Instance); + var executor = new EnableLoggerArgumentExecutor(_runSettingsManager); executor.Initialize("logger://DummyLoggerUri;Collection=http://localhost:8080/tfs/DefaultCollection;TeamProject=MyProject;BuildName=DailyBuild_20121130.1"); string expectedSettingsXml = @@ -319,7 +313,7 @@ public void ExecutorInitializeShouldCorrectlyAddLoggerInRunSettingsWhenOtherLogg "; - Assert.AreEqual(expectedSettingsXml, RunSettingsManager.Instance.ActiveRunSettings?.SettingsXml); + Assert.AreEqual(expectedSettingsXml, _runSettingsManager.ActiveRunSettings?.SettingsXml); } [TestMethod] @@ -354,9 +348,9 @@ public void ExecutorInitializeShouldPreferCommandLineLoggerOverRunSettingsLogger var runSettings = new RunSettings(); runSettings.LoadSettingsXml(settingsXml); - RunSettingsManager.Instance.SetActiveRunSettings(runSettings); + _runSettingsManager.SetActiveRunSettings(runSettings); - var executor = new EnableLoggerArgumentExecutor(RunSettingsManager.Instance); + var executor = new EnableLoggerArgumentExecutor(_runSettingsManager); executor.Initialize("tempLogger2"); string expectedSettingsXml = @@ -385,7 +379,7 @@ public void ExecutorInitializeShouldPreferCommandLineLoggerOverRunSettingsLogger "; - Assert.AreEqual(expectedSettingsXml, RunSettingsManager.Instance.ActiveRunSettings?.SettingsXml); + Assert.AreEqual(expectedSettingsXml, _runSettingsManager.ActiveRunSettings?.SettingsXml); } [TestMethod] @@ -420,9 +414,9 @@ public void ExecutorInitializeShouldPreferCommandLineLoggerOverRunSettingsLogger var runSettings = new RunSettings(); runSettings.LoadSettingsXml(settingsXml); - RunSettingsManager.Instance.SetActiveRunSettings(runSettings); + _runSettingsManager.SetActiveRunSettings(runSettings); - var executor = new EnableLoggerArgumentExecutor(RunSettingsManager.Instance); + var executor = new EnableLoggerArgumentExecutor(_runSettingsManager); executor.Initialize("tempLoggER2"); string expectedSettingsXml = @@ -451,7 +445,7 @@ public void ExecutorInitializeShouldPreferCommandLineLoggerOverRunSettingsLogger "; - Assert.AreEqual(expectedSettingsXml, RunSettingsManager.Instance.ActiveRunSettings?.SettingsXml); + Assert.AreEqual(expectedSettingsXml, _runSettingsManager.ActiveRunSettings?.SettingsXml); } [TestMethod] @@ -494,9 +488,9 @@ public void ExecutorInitializeShouldPreferCommandLineLoggerWithParamsOverRunSett var runSettings = new RunSettings(); runSettings.LoadSettingsXml(settingsXml); - RunSettingsManager.Instance.SetActiveRunSettings(runSettings); + _runSettingsManager.SetActiveRunSettings(runSettings); - var executor = new EnableLoggerArgumentExecutor(RunSettingsManager.Instance); + var executor = new EnableLoggerArgumentExecutor(_runSettingsManager); executor.Initialize("logger://DummyLoggerUri;Collection=http://localhost:8080/tfs/DefaultCollectionOverride;TeamProjectOverride=MyProject;BuildName=DailyBuild_20121130.1Override;NewAttr=value"); string expectedSettingsXml = @@ -533,6 +527,6 @@ public void ExecutorInitializeShouldPreferCommandLineLoggerWithParamsOverRunSett "; - Assert.AreEqual(expectedSettingsXml, RunSettingsManager.Instance.ActiveRunSettings?.SettingsXml); + Assert.AreEqual(expectedSettingsXml, _runSettingsManager.ActiveRunSettings?.SettingsXml); } } diff --git a/test/vstest.console.UnitTests/Processors/TestAdapterLoadingStrategyArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/TestAdapterLoadingStrategyArgumentProcessorTests.cs index a5945d8784..35689e43fc 100644 --- a/test/vstest.console.UnitTests/Processors/TestAdapterLoadingStrategyArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/TestAdapterLoadingStrategyArgumentProcessorTests.cs @@ -18,23 +18,11 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] -// Because runsettings tests use the instance of RunSettingsManager which is static. [DoNotParallelize] public class TestAdapterLoadingStrategyArgumentProcessorTests { private readonly CommandLineOptions _commandLineOptions = new(); - private readonly RunSettings _currentActiveSetting; - - public TestAdapterLoadingStrategyArgumentProcessorTests() - { - _currentActiveSetting = RunSettingsManager.Instance.ActiveRunSettings; - } - - [TestCleanup] - public void TestClean() - { - RunSettingsManager.Instance.SetActiveRunSettings(_currentActiveSetting); - } + private readonly RunSettingsManager _runSettingsManager = new(); [TestMethod] [TestCategory("Windows")] @@ -43,17 +31,17 @@ public void InitializeShouldHonorEnvironmentVariablesInTestAdapterPaths() var runSettingsXml = "%temp%\\adapters1;%temp%\\adapters2"; var runSettings = new RunSettings(); runSettings.LoadSettingsXml(runSettingsXml); - RunSettingsManager.Instance.SetActiveRunSettings(runSettings); + _runSettingsManager.SetActiveRunSettings(runSettings); var mockFileHelper = new Mock(); var mockOutput = new Mock(); mockFileHelper.Setup(x => x.DirectoryExists(It.IsAny())).Returns(true); mockFileHelper.Setup(x => x.GetFullPath(It.IsAny())).Returns((Func)(s => Path.GetFullPath(s))); - var executor = new TestAdapterLoadingStrategyArgumentExecutor(_commandLineOptions, RunSettingsManager.Instance, mockOutput.Object, mockFileHelper.Object); + var executor = new TestAdapterLoadingStrategyArgumentExecutor(_commandLineOptions, _runSettingsManager, mockOutput.Object, mockFileHelper.Object); executor.Initialize(nameof(TestAdapterLoadingStrategy.Default)); - var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(RunSettingsManager.Instance.ActiveRunSettings.SettingsXml); + var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(_runSettingsManager.ActiveRunSettings.SettingsXml); var tempPath = Path.GetFullPath(Environment.ExpandEnvironmentVariables("%temp%")); Assert.AreEqual($"{tempPath}\\adapters1;{tempPath}\\adapters2", runConfiguration.TestAdaptersPaths); @@ -66,13 +54,13 @@ public void InitializeShouldAddRightAdapterPathInErrorMessage() var runSettingsXml = "d:\\users"; var runSettings = new RunSettings(); runSettings.LoadSettingsXml(runSettingsXml); - RunSettingsManager.Instance.SetActiveRunSettings(runSettings); + _runSettingsManager.SetActiveRunSettings(runSettings); var mockFileHelper = new Mock(); var mockOutput = new Mock(); mockFileHelper.Setup(x => x.DirectoryExists("d:\\users")).Returns(false); mockFileHelper.Setup(x => x.DirectoryExists("c:\\users")).Returns(true); - var executor = new TestAdapterLoadingStrategyArgumentExecutor(_commandLineOptions, RunSettingsManager.Instance, mockOutput.Object, mockFileHelper.Object); + var executor = new TestAdapterLoadingStrategyArgumentExecutor(_commandLineOptions, _runSettingsManager, mockOutput.Object, mockFileHelper.Object); var message = "The path 'd:\\users' specified in the 'TestAdapterPath' is invalid. Error: The custom test adapter search path provided was not found, provide a valid path and try again."; @@ -89,10 +77,10 @@ public void InitializeShouldThrowIfPathDoesNotExist() var runSettings = new RunSettings(); runSettings.LoadSettingsXml(runSettingsXml); - RunSettingsManager.Instance.SetActiveRunSettings(runSettings); + _runSettingsManager.SetActiveRunSettings(runSettings); var mockOutput = new Mock(); - var executor = new TestAdapterLoadingStrategyArgumentExecutor(_commandLineOptions, RunSettingsManager.Instance, mockOutput.Object, new FileHelper()); + var executor = new TestAdapterLoadingStrategyArgumentExecutor(_commandLineOptions, _runSettingsManager, mockOutput.Object, new FileHelper()); var message = $"The path '{folder}' specified in the 'TestAdapterPath' is invalid. Error: The custom test adapter search path provided was not found, provide a valid path and try again."; diff --git a/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs b/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs index 816921e443..9bb2858915 100644 --- a/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs +++ b/test/vstest.console.UnitTests/Processors/TestAdapterPathArgumentProcessorTests.cs @@ -22,23 +22,11 @@ namespace Microsoft.VisualStudio.TestPlatform.CommandLine.UnitTests.Processors; [TestClass] -// Because runsettings tests use the instance of RunSettingsManager which is static. [DoNotParallelize] public class TestAdapterPathArgumentProcessorTests { private readonly CommandLineOptions _commandLineOptions = new(); - private readonly RunSettings _currentActiveSetting; - - public TestAdapterPathArgumentProcessorTests() - { - _currentActiveSetting = RunSettingsManager.Instance.ActiveRunSettings; - } - - [TestCleanup] - public void TestClean() - { - RunSettingsManager.Instance.SetActiveRunSettings(_currentActiveSetting); - } + private readonly RunSettingsManager _runSettingsManager = new(); [TestMethod] @@ -109,16 +97,16 @@ public void InitializeShouldThrowIfArgumentIsAWhiteSpace() [TestMethod] public void InitializeShouldUpdateTestAdapterPathInRunSettings() { - RunSettingsManager.Instance.AddDefaultRunSettings(); + _runSettingsManager.AddDefaultRunSettings(); var mockOutput = new Mock(); - var executor = new TestAdapterPathArgumentExecutor(_commandLineOptions, RunSettingsManager.Instance, mockOutput.Object, new FileHelper()); + var executor = new TestAdapterPathArgumentExecutor(_commandLineOptions, _runSettingsManager, mockOutput.Object, new FileHelper()); var currentAssemblyPath = typeof(TestAdapterPathArgumentExecutor).Assembly.Location; var currentFolder = Path.GetDirectoryName(currentAssemblyPath); executor.Initialize(currentFolder); - var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(RunSettingsManager.Instance.ActiveRunSettings.SettingsXml); + var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(_runSettingsManager.ActiveRunSettings.SettingsXml); Assert.AreEqual(currentFolder, runConfiguration.TestAdaptersPaths); } @@ -129,15 +117,15 @@ public void InitializeShouldMergeTestAdapterPathsInRunSettings() var runSettingsXml = "d:\\users;f:\\users"; var runSettings = new RunSettings(); runSettings.LoadSettingsXml(runSettingsXml); - RunSettingsManager.Instance.SetActiveRunSettings(runSettings); + _runSettingsManager.SetActiveRunSettings(runSettings); var mockFileHelper = new Mock(); var mockOutput = new Mock(); mockFileHelper.Setup(x => x.DirectoryExists(It.IsAny())).Returns(true); - var executor = new TestAdapterPathArgumentExecutor(_commandLineOptions, RunSettingsManager.Instance, mockOutput.Object, mockFileHelper.Object); + var executor = new TestAdapterPathArgumentExecutor(_commandLineOptions, _runSettingsManager, mockOutput.Object, mockFileHelper.Object); executor.Initialize("c:\\users"); - var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(RunSettingsManager.Instance.ActiveRunSettings.SettingsXml); + var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(_runSettingsManager.ActiveRunSettings.SettingsXml); Assert.AreEqual("d:\\users;f:\\users;c:\\users", runConfiguration.TestAdaptersPaths); } @@ -148,15 +136,15 @@ public void InitializeShouldTrimTrailingAndLeadingDoubleQuotes() var runSettingsXml = "d:\\users"; var runSettings = new RunSettings(); runSettings.LoadSettingsXml(runSettingsXml); - RunSettingsManager.Instance.SetActiveRunSettings(runSettings); + _runSettingsManager.SetActiveRunSettings(runSettings); var mockFileHelper = new Mock(); var mockOutput = new Mock(); mockFileHelper.Setup(x => x.DirectoryExists(It.IsAny())).Returns(true); - var executor = new TestAdapterPathArgumentExecutor(_commandLineOptions, RunSettingsManager.Instance, mockOutput.Object, mockFileHelper.Object); + var executor = new TestAdapterPathArgumentExecutor(_commandLineOptions, _runSettingsManager, mockOutput.Object, mockFileHelper.Object); executor.Initialize("\"c:\\users\""); - var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(RunSettingsManager.Instance.ActiveRunSettings.SettingsXml); + var runConfiguration = XmlRunSettingsUtilities.GetRunConfigurationNode(_runSettingsManager.ActiveRunSettings.SettingsXml); Assert.AreEqual("d:\\users;c:\\users", runConfiguration.TestAdaptersPaths); } From b6da3328d1c5dc236f0cf3e288d214762fd531e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Jare=C5=A1?= Date: Tue, 14 Jul 2026 12:14:32 +0200 Subject: [PATCH 63/87] Add .github/release.yml to exclude bot authors from release notes (#16280) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/release.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/release.yml diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000000..4bf3e90010 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,8 @@ +changelog: + exclude: + authors: + - dotnet-bot + - dotnet-maestro + - dotnet-maestro[bot] + - dependabot + - dependabot[bot] From 723ba5fbe809dd0539079c2436ab83db33548d72 Mon Sep 17 00:00:00 2001 From: Azat Mukhametshin Date: Tue, 14 Jul 2026 13:15:58 +0200 Subject: [PATCH 64/87] Docs: remove orphaned files (TODO.md, Problems.md, roadmap.md) (#16275) All three are orphaned (no inbound links in README, toc.yml, or docfx config): - TODO.md: dead placeholder (incomplete C# code fragment). - Problems.md: internal protocol/IPC scratch notes; content preserved in git history and in the PR description for maintainer triage. - roadmap.md: stale 2017-era roadmap (TPV2/VSTS/UWP/Win10 IoT); superseded by releases.md, which README already links. Copilot-Session: 4e840b63-ad95-40c2-9ef8-5f2fcc4cfdaa Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/Problems.md | 11 ----------- docs/TODO.md | 20 -------------------- docs/roadmap.md | 31 ------------------------------- 3 files changed, 62 deletions(-) delete mode 100644 docs/Problems.md delete mode 100644 docs/TODO.md delete mode 100644 docs/roadmap.md diff --git a/docs/Problems.md b/docs/Problems.md deleted file mode 100644 index 877d78c1a7..0000000000 --- a/docs/Problems.md +++ /dev/null @@ -1,11 +0,0 @@ -# Problems and inconsistencies - -Client connection to runner, does not detect that we are connected in the same way as runner to testhost. - -Client to runner needs a message sent back (testsession.connected), while testhost does not need any message because the underlying tcp client will detect that testhost connected and will send it work right away. What is the unified way to do this? - -There are some requests that need no response, like extensions initialize. - -Translation layer uses 4 different messages for run, but sends the same payload in all of them with mixed info that is not necessary for some of the messages (testcases / sources, isDebug). When running with sources and default testhost launcher, we send (from translation layer) incorrectly the same message as when running with testcases. - -The messages between runner and testhost have the same names, and sometimes different payloads. diff --git a/docs/TODO.md b/docs/TODO.md deleted file mode 100644 index d172c42e8a..0000000000 --- a/docs/TODO.md +++ /dev/null @@ -1,20 +0,0 @@ -Structures: - - // Test adapter and array of sources map: - // { - // C:\temp\testAdapter1.dll : [ source1.dll, source2.dll ], - // C:\temp\testadapter2.dll : [ source3.dll, source2.dll ] - // } - public Dictionary> AdapterSourceMap - - - TimeSpan - - string? RunSettings - - - string? TestCaseFilter - - TestSessionInfo? TestSessionInfo - - TestPlatformOptions \ No newline at end of file diff --git a/docs/roadmap.md b/docs/roadmap.md deleted file mode 100644 index 001a8cf70e..0000000000 --- a/docs/roadmap.md +++ /dev/null @@ -1,31 +0,0 @@ -# Test Platform Roadmap - -This repo is the modern, OSS, cross-plat testing engine that has been powering testing on .NET Core via the "test" verb in dotnet test, and Live Unit Testing scenario (LUT) in Visual Studio. Internally we call this repo "TPV2" (Test Platform V2) - -We aim to continuously deliver improvements that will ship with Visual Studio and with the .NET Tools SDK. These improvements are directly informed by your feedback filed as [issues](https://github.com/Microsoft/vstest/issues). If you do not see your issue addressed already, we will get to it soon! If you would like to help out, let us know! - -Over the past several quarters, we have made many enhancements - from introducing support for Mono, to refactoring the platform to make it ready to support device testing, to performance improvements, to enabling robust C++ support, to improved documentation, and more. For a complete list see here: [Release Notes](./releases.md) - -## Roadmap - -We typically plan for a quarter, and establish a set of themes we want to work towards. Here are the themes we will work on this quarter. - -### Reach: Enable leveraging your vstest experience across all supported platforms - -Over the course of the next phase of execution we will make TPV2 the "default" for all scenarios across Visual Studio and Visual Studio Team Services (VSTS) – i.e. extending it to .NET Framework, UWP, and the VSTest task in VSTS. We will ship a standalone package that can be potentially used in other CI systems even. This is a big switch. We will strive to maintain backwards compat, and publish migration guides for the few features that require to be migrated, and help you in the migration. - -### Performance: At scale - -Performance has been an area where we have received feedback, and made strong progress as well. It will continue to remain a focus. We will look to make improvements across the pipeline from the Test Explorer to the framework adapters, to enhance the overall end to end performance. - -### UWP, Win10 IoT Core Support - -UWP is the application platform for Windows 10, to reach all Windows 10 devices – PC, tablet, phone, Xbox, HoloLens, Surface Hub and more. The vstest engine is architected so that it can be extended to support new application platforms. Such extensions will come from teams who understand their platforms the best, and integrated with vstest. To drive home this point, vstest will be extended to support testing UWP applications. In particular we will light up support for Win10 IoT Core. - -### Code Coverage for .NET Core - -This has been a clear ask from the community, and we are working towards enabling this support. The code coverage infrastructure consumes information from PDB files. Specifically with regard to .NET Core, it now needs to understand the new portable PDB format. We are working cross-team to introduce this support in order to light up code coverage support for .NET Core. - -## Summary - -These are examples of the work we will be focusing on this quarter. We will provide details through individual issues. Follow along, and let us know what you think. We look forward to working with you! From ae6c3a0d2d459bd807068faefa734ac8056ef6e2 Mon Sep 17 00:00:00 2001 From: Azat Mukhametshin Date: Tue, 14 Jul 2026 13:58:32 +0200 Subject: [PATCH 65/87] Docs: fully document the blame data collector (#16276) Expand docs/extensions/blame-datacollector.md beyond /blame + Sequence.xml: - Full crash-dump and hang-dump option matrix (CollectDump/CollectHangDump, DumpType, CollectAlways, TestTimeout, HangDumpType) with defaults, and the equivalent dotnet test switches (--blame-crash*, --blame-hang*). - ProcDump requirement, PROCDUMP_PATH / VSTEST_DUMP_FORCEPROCDUMP, and the managed-dump platform/TFM support matrix. - runsettings DataCollector (friendlyName blame) configuration example. - Microsoft.Testing.Platform equivalent note (--crashdump / --hangdump). Copilot-Session: 4e840b63-ad95-40c2-9ef8-5f2fcc4cfdaa Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/extensions/blame-datacollector.md | 120 +++++++++++++++++++++++-- 1 file changed, 115 insertions(+), 5 deletions(-) diff --git a/docs/extensions/blame-datacollector.md b/docs/extensions/blame-datacollector.md index 91ad538ed1..b4b6b2181f 100644 --- a/docs/extensions/blame-datacollector.md +++ b/docs/extensions/blame-datacollector.md @@ -2,13 +2,118 @@ Certain execution sequences can crash the testhost process spawned by the vstest runner. However there is no easy way to diagnose such an aborted test run since there is no way to know what specific test case was running at the time. The "blame" mode in vstest tracks the tests as they are executing and, in the case of the testhost process crashing, emits the tests names in their sequence of execution up to and including the specific test that was running at the time of the crash. This makes it easier to isolate the offending test and diagnose further. # Syntax -```vstest.console.exe /blame``` or ```dotnet test --blame``` + +Blame can be enabled from either `vstest.console.exe`, `dotnet test`, or runsettings. The simplest form records the test execution sequence: + +```shell +vstest.console.exe MyTests.dll /Blame +dotnet test --blame +``` + +Use the canonical `vstest.console.exe` switch form to collect crash or hang dumps: + +```text +/Blame:[CollectDump];[CollectAlways]=[value];[DumpType]=[value] +/Blame:[CollectHangDump];TestTimeout=[value];[HangDumpType]=[value] +``` + +For example: + +```shell +vstest.console.exe MyTests.dll /Blame:CollectDump;CollectAlways=true;DumpType=full +vstest.console.exe MyTests.dll /Blame:CollectHangDump;TestTimeout=90m;HangDumpType=mini +``` + +The switch name is `/Blame`; option names are separated with semicolons. Crash dump parameters apply to `CollectDump`; hang dump parameters apply to `CollectHangDump`. + +# Options + +## Crash dump options + +| Option | Values | Default | dotnet test switch | runsettings element | +| --- | --- | --- | --- | --- | +| `CollectDump` | Present or absent | Off | `--blame-crash` | `` | +| `CollectAlways` | `true`, `false` | `false` | `--blame-crash-collect-always` | `` | +| `DumpType` | `mini`, `full` | `full` when `/Blame:CollectDump` is used | `--blame-crash-dump-type full\|mini` | `` | + +Examples: + +```shell +vstest.console.exe MyTests.dll /Blame:CollectDump;CollectAlways=true;DumpType=mini +dotnet test --blame-crash --blame-crash-collect-always --blame-crash-dump-type mini +``` + +## Hang dump options + +| Option | Values | Default | dotnet test switch | runsettings element | +| --- | --- | --- | --- | --- | +| `CollectHangDump` | Present or absent | Off | `--blame-hang` | Use `` | +| `TestTimeout` | Time span such as `1.5h`, `90m`, `5400s`, `5400000ms`; unitless values are milliseconds | `1h` when `/Blame:CollectHangDump` is used | `--blame-hang-timeout ` | `` | +| `HangDumpType` | `mini`, `full`, `none` | `full` when `/Blame:CollectHangDump` is used | `--blame-hang-dump-type full\|mini\|none` | `` | +| `DumpType` | `mini`, `full`, `none` | Prefer `HangDumpType` | Not applicable | Backward-compatible alias on `` | + +Examples: + +```shell +vstest.console.exe MyTests.dll /Blame:CollectHangDump;TestTimeout=5400s;HangDumpType=full +dotnet test --blame-hang --blame-hang-timeout 90m --blame-hang-dump-type full +``` + +Use `HangDumpType=none` when you want blame to abort a run after the hang timeout and still produce the sequence file, but do not want a dump file. + +## Additional configuration keys + +| Option | Values | Default | dotnet test switch | runsettings element | +| --- | --- | --- | --- | --- | +| `CollectDumpOnTestSessionHang` | Element name | Off | Configured by `--blame-hang` | `` | +| `MonitorPostmortemDebugger` | Element name with `DumpDirectoryPath` attribute | Off | No direct equivalent | `` | +| `Framework` | Target framework moniker, for example `.NETCoreApp,Version=v8.0` | Supplied by the test platform | No direct equivalent | `...` | + +`MonitorPostmortemDebugger` is intended for scenarios that monitor an external postmortem debugger dump directory. Most users should use `CollectDump` or `CollectDumpOnTestSessionHang` instead. + +# Runsettings example + +```xml + + + + + + + + + + + + + +``` + +# Dump collection support + +For managed testhost crashes on .NET 5 and later, VSTest can collect crash dumps automatically on Windows, macOS, and Linux. Native code crashes, and crashes on .NET Core 3.1 or earlier, require ProcDump: + +- Windows: `procdump.exe` or `procdump64.exe` +- Linux/macOS: `procdump` + +Put ProcDump on `PATH`, or set `PROCDUMP_PATH` to the directory that contains the executable. Set `VSTEST_DUMP_FORCEPROCDUMP=1` to force ProcDump-based collection on .NET 5 and later. + +Hang dumps are supported for these target frameworks and platforms: + +| Platform | Target framework support | +| --- | --- | +| Windows | `netcoreapp2.1` and later | +| Linux | `netcoreapp3.1` and later | +| macOS | `net5.0` and later | # Output -If the testhost process had crashed, then the fully qualified names of the tests in their sequence of execution up to and including the specific test that was running at the time of the crash, is emitted into a sequence.xml file created under the TestResults folder. -## Example -Here is an example of the emitted xml file. +Blame writes attachments under the run's results directory, typically `TestResults//`. + +- `Sequence.xml` records the tests that started, in execution order. If a testhost crashes or hangs, the last test listed is usually the test that was running at the time. +- `*.dmp` files are written to the same run-specific results directory when dump collection is enabled. + +Here is an example of the emitted sequence file. ```xml @@ -17,4 +122,9 @@ Here is an example of the emitted xml file. ``` -In this case, the listed last is the test that was running at the time of the crash. + +In this case, the `` listed last is the test that was running at the time of the crash. + +# Microsoft.Testing.Platform (MTP) equivalent + +Microsoft.Testing.Platform does not use `/Blame` or the VSTest blame data collector. MTP projects use `--crashdump`, `--hangdump`, and `--hangdump-timeout` from the `Microsoft.Testing.Extensions.CrashDump` and `Microsoft.Testing.Extensions.HangDump` packages. From 4294cde1c9b4a740ce0f3d84ae58e8b0eee4c1d0 Mon Sep 17 00:00:00 2001 From: Azat Mukhametshin Date: Tue, 14 Jul 2026 14:02:18 +0200 Subject: [PATCH 66/87] Inject runsettings environment variables on the MTP execution path (#16283) --- .../Client/MTP/MtpProxyExecutionManager.cs | 29 ++++++++++ .../MtpUnderVstestTests.cs | 54 ++++++++++++++++--- test/TestAssets/MtpMSTestProject/UnitTests.cs | 18 +++++++ 3 files changed, 95 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyExecutionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyExecutionManager.cs index da5b6d0726..bb96412234 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyExecutionManager.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyExecutionManager.cs @@ -16,6 +16,7 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Engine; +using Microsoft.VisualStudio.TestPlatform.Utilities; namespace Microsoft.VisualStudio.TestPlatform.CrossPlatEngine.Client.MTP; @@ -79,6 +80,13 @@ public int StartTestRun(TestRunCriteria testRunCriteria, IInternalTestRunEventsH int processId = 0; bool aborted = false; + // Inject environment variables declared in the runsettings RunConfiguration/EnvironmentVariables + // into the MTP application launch. On the classic path ProxyOperationManager reads these from the + // runsettings and passes them to the testhost process; the MTP application is its own host, so we + // apply them here. Done before BeforeTestRun so datacollector-provided profiler variables merge on + // top and win on collision (matching the classic ordering). + ApplyRunSettingsEnvironmentVariables(testRunCriteria.TestRunSettings); + BeforeTestRun(eventHandler); foreach (var (source, tests) in BuildWork(testRunCriteria)) @@ -371,6 +379,27 @@ private int RunSource( .Select(source => (source, (List?)null)); } + /// + /// Reads the environment variables declared in the runsettings + /// RunConfiguration/EnvironmentVariables and merges them into + /// so they are applied to the MTP application launch. + /// + private void ApplyRunSettingsEnvironmentVariables(string? runSettings) + { + Dictionary? runSettingsEnvironmentVariables = InferRunSettingsHelper.GetEnvironmentVariables(runSettings); + if (runSettingsEnvironmentVariables is null || runSettingsEnvironmentVariables.Count == 0) + { + return; + } + + EnvironmentVariables ??= new Dictionary( + Environment.OSVersion.Platform == PlatformID.Win32NT ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal); + foreach (KeyValuePair variable in runSettingsEnvironmentVariables) + { + EnvironmentVariables[variable.Key] = variable.Value; + } + } + private static List> BuildTestsFilter(List tests) => tests .Select(test => new Dictionary diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MtpUnderVstestTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MtpUnderVstestTests.cs index 504ff173b3..2e501c8eb9 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MtpUnderVstestTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MtpUnderVstestTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System.Collections.Generic; using System.IO; using Microsoft.TestPlatform.TestUtilities; @@ -45,7 +46,7 @@ public void RunMtpApplicationExecutesTestsOverMtpProtocol(RunnerInfo runnerInfo) InvokeVsTest(arguments); - ValidateSummaryStatus(2, 1, 1); + ValidateSummaryStatus(3, 1, 1); } [TestMethod] @@ -67,13 +68,13 @@ public void RunMixedClassicAndMtpApplicationsInSingleRun(RunnerInfo runnerInfo) InvokeVsTest(arguments); - // Classic 1/1/1 + MTP 2/1/1 aggregated into one run summary. - ValidateSummaryStatus(3, 2, 2); + // Classic 1/1/1 + MTP 3/1/1 aggregated into one run summary. + ValidateSummaryStatus(4, 2, 2); } [TestMethod] // Prove a TRX logger aggregates results from both the classic and the MTP source in a mixed run into a - // single .trx with all seven tests. + // single .trx with all eight tests. [TestMatrix(testHost: Target.Net)] public void RunMixedClassicAndMtpApplicationsWritesSingleTrx(RunnerInfo runnerInfo) { @@ -91,7 +92,7 @@ public void RunMixedClassicAndMtpApplicationsWritesSingleTrx(RunnerInfo runnerIn InvokeVsTest(arguments); - ValidateSummaryStatus(3, 2, 2); + ValidateSummaryStatus(4, 2, 2); var trxPath = Path.Combine(TempDirectory.Path, trxFileName); Assert.IsTrue(File.Exists(trxPath), "Expected a single TRX to be written for the mixed run at '{0}'.", trxPath); @@ -120,6 +121,47 @@ public void RunMtpApplicationWithBlameCompletesRun(RunnerInfo runnerInfo) InvokeVsTest(arguments); - ValidateSummaryStatus(2, 1, 1); + ValidateSummaryStatus(3, 1, 1); + } + + [TestMethod] + // Environment variables declared in a runsettings RunConfiguration/EnvironmentVariables block must be + // injected into the self-hosted MTP process. There is no testhost here, so vstest.console applies them + // to the MTP application launch. The guarded RunSettingsEnvironmentVariableIsInjected test asserts the + // injected value; CHECK_RUNSETTINGS_VAR is passed as a process env var (inherited by the host) to opt + // the check in, so the run passes only when runsettings injection actually delivered the value. If the + // value did not reach the host that test fails and the summary would be 2/2/1 instead of 3/1/1. + [TestMatrix(testHost: Target.Net)] + public void RunMtpApplicationInjectsRunSettingsEnvironmentVariables(RunnerInfo runnerInfo) + { + SetTestEnvironment(_testEnvironment, runnerInfo); + + var runsettingsXml = @" + + + mtp-runsettings-value + + + "; + var runsettingsPath = Path.Combine(TempDirectory.Path, "mtp_env_" + System.Guid.NewGuid() + ".runsettings"); + File.WriteAllText(runsettingsPath, runsettingsXml); + + var arguments = PrepareArguments( + GetAssetFullPath(MtpApp), + testAdapterPath: null, + runSettings: runsettingsPath, + FrameworkArgValue, + runnerInfo.InIsolationValue, + resultsDirectory: TempDirectory.Path); + + var env = new Dictionary + { + ["CHECK_RUNSETTINGS_VAR"] = "1", + }; + + InvokeVsTest(arguments, env); + + // The guarded test passes only if MTP_FROM_RUNSETTINGS reached the host with the runsettings value. + ValidateSummaryStatus(3, 1, 1); } } diff --git a/test/TestAssets/MtpMSTestProject/UnitTests.cs b/test/TestAssets/MtpMSTestProject/UnitTests.cs index d6846e4a9f..c0d02ed3da 100644 --- a/test/TestAssets/MtpMSTestProject/UnitTests.cs +++ b/test/TestAssets/MtpMSTestProject/UnitTests.cs @@ -36,4 +36,22 @@ public void TestSkipped() { Assert.Fail("should never run"); } + + // Verifies that environment variables declared in a runsettings RunConfiguration/EnvironmentVariables + // block are injected into the self-hosted MTP process. The check is opted into by the + // CHECK_RUNSETTINGS_VAR control variable, which the env-var acceptance test passes as a *process* + // environment variable (inherited by the host regardless of the fix), so the guard is decisive: when + // runsettings injection works MTP_FROM_RUNSETTINGS carries the expected value and the test passes; when + // it is broken the variable is absent and the assert fails. In every other run the control variable is + // unset, so the test is a no-op and stays green. + [TestMethod] + public void RunSettingsEnvironmentVariableIsInjected() + { + if (System.Environment.GetEnvironmentVariable("CHECK_RUNSETTINGS_VAR") != "1") + { + return; + } + + Assert.AreEqual("mtp-runsettings-value", System.Environment.GetEnvironmentVariable("MTP_FROM_RUNSETTINGS")); + } } From f48a80f2c63bd107965d811b8e5d14f59ba065e6 Mon Sep 17 00:00:00 2001 From: Azat Mukhametshin Date: Tue, 14 Jul 2026 14:03:16 +0200 Subject: [PATCH 67/87] Surface per-test standard output/error on the MTP execution path (#16284) --- .../Client/MTP/MtpConstants.cs | 2 ++ .../Client/MTP/MtpTestNodeConverter.cs | 15 +++++++++ .../MtpUnderVstestTests.cs | 31 +++++++++++++++++++ test/TestAssets/MtpMSTestProject/UnitTests.cs | 4 +++ 4 files changed, 52 insertions(+) diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpConstants.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpConstants.cs index 1189fc3fec..0e43f0a5ca 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpConstants.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpConstants.cs @@ -45,6 +45,8 @@ internal static class MtpConstants public const string TimeDurationMs = "time.duration-ms"; public const string ErrorMessage = "error.message"; public const string ErrorStackTrace = "error.stacktrace"; + public const string StandardOutput = "standardOutput"; + public const string StandardError = "standardError"; public const string LocationFile = "location.file"; public const string LocationLineStart = "location.line-start"; public const string Traits = "traits"; diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpTestNodeConverter.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpTestNodeConverter.cs index cf7e3fe67e..d84b11c4d2 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpTestNodeConverter.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpTestNodeConverter.cs @@ -82,6 +82,21 @@ public static TestResult ToTestResult(JsonObject node, string source) result.Duration = TimeSpan.FromMilliseconds(durationMs); } + // Surface the test's captured standard output/error (when the MTP node carries it) as result + // messages so the console and TRX loggers show it, matching the classic path where a test's + // stdout/stderr is attached to its result. + string? standardOutput = MtpJson.GetString(node, MtpConstants.StandardOutput); + if (!string.IsNullOrEmpty(standardOutput)) + { + result.Messages.Add(new TestResultMessage(TestResultMessage.StandardOutCategory, standardOutput)); + } + + string? standardError = MtpJson.GetString(node, MtpConstants.StandardError); + if (!string.IsNullOrEmpty(standardError)) + { + result.Messages.Add(new TestResultMessage(TestResultMessage.StandardErrorCategory, standardError)); + } + return result; } diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MtpUnderVstestTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MtpUnderVstestTests.cs index 2e501c8eb9..4476fcc41a 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MtpUnderVstestTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MtpUnderVstestTests.cs @@ -164,4 +164,35 @@ public void RunMtpApplicationInjectsRunSettingsEnvironmentVariables(RunnerInfo r // The guarded test passes only if MTP_FROM_RUNSETTINGS reached the host with the runsettings value. ValidateSummaryStatus(3, 1, 1); } + + [TestMethod] + // A test's captured standard output/error must be surfaced to the loggers. The MTP node carries the + // per-test standardOutput/standardError; MtpTestNodeConverter now maps them onto the vstest result so + // the console and TRX show them. Before this the markers appeared nowhere. TestPassesToo writes the + // markers; the run produces a TRX that must contain them. + [TestMatrix(testHost: Target.Net)] + public void RunMtpApplicationSurfacesPerTestStandardOutput(RunnerInfo runnerInfo) + { + SetTestEnvironment(_testEnvironment, runnerInfo); + + var trxFileName = "stdout.trx"; + var arguments = PrepareArguments( + GetAssetFullPath(MtpApp), + testAdapterPath: null, + runSettings: string.Empty, + FrameworkArgValue, + runnerInfo.InIsolationValue, + resultsDirectory: TempDirectory.Path); + arguments = string.Concat(arguments, $" /logger:trx;LogFileName={trxFileName}"); + + InvokeVsTest(arguments); + + ValidateSummaryStatus(2, 1, 1); + + var trxPath = Path.Combine(TempDirectory.Path, trxFileName); + Assert.IsTrue(File.Exists(trxPath), "Expected a TRX at '{0}'.", trxPath); + var trx = File.ReadAllText(trxPath); + Assert.Contains("MTP_STDOUT_MARKER", trx, "Expected the test's standard output to be surfaced into the TRX."); + Assert.Contains("MTP_STDERR_MARKER", trx, "Expected the test's standard error to be surfaced into the TRX."); + } } diff --git a/test/TestAssets/MtpMSTestProject/UnitTests.cs b/test/TestAssets/MtpMSTestProject/UnitTests.cs index c0d02ed3da..2c625e7a01 100644 --- a/test/TestAssets/MtpMSTestProject/UnitTests.cs +++ b/test/TestAssets/MtpMSTestProject/UnitTests.cs @@ -21,6 +21,10 @@ public void TestPasses() [TestMethod] public void TestPassesToo() { + // Writes to stdout and stderr (and still passes) so the MTP-under-vstest stdout acceptance test can + // assert the captured per-test output is surfaced to the console and TRX loggers. + System.Console.WriteLine("MTP_STDOUT_MARKER"); + System.Console.Error.WriteLine("MTP_STDERR_MARKER"); Assert.AreEqual(2, Add(1, 1)); } From f0c2756f60c082192737f4042c8e335e1229f095 Mon Sep 17 00:00:00 2001 From: Azat Mukhametshin Date: Wed, 15 Jul 2026 10:50:00 +0200 Subject: [PATCH 68/87] Surface out-of-process data collector messages on the MTP execution path (#16272) --- .../Client/MTP/MtpProxyExecutionManager.cs | 44 +++++++++++- .../MtpUnderVstestTests.cs | 70 ++++++++++++++++++- 2 files changed, 109 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyExecutionManager.cs b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyExecutionManager.cs index bb96412234..0fe50c394e 100644 --- a/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyExecutionManager.cs +++ b/src/Microsoft.TestPlatform.CrossPlatEngine/Client/MTP/MtpProxyExecutionManager.cs @@ -114,6 +114,13 @@ public int StartTestRun(TestRunCriteria testRunCriteria, IInternalTestRunEventsH AfterTestRun(attachments, invokedDataCollectors); + // Surface the data collector messages produced during the run and at session end (e.g. + // per-test-case notifications, warnings/errors, the Blame sequence-file path, disposal). They + // are buffered on the run events handler while the run is in flight and delivered during the + // AfterTestRunEnd exchange; the classic path relays them live, so on this path we flush them + // once the run is done. Without this they are silently dropped. + SurfaceDataCollectionMessages(eventHandler); + TestRunStatistics finalStats = aggregate.Snapshot(); var completeArgs = new TestRunCompleteEventArgs( finalStats, @@ -188,12 +195,43 @@ private void BeforeTestRun(IInternalTestRunEventsHandler eventHandler) } // Surface any messages the data collector produced while starting up. - foreach (Tuple message in _dataCollectionEventsHandler!.Messages) + SurfaceDataCollectionMessages(eventHandler); + } + + /// + /// Flushes any data collector log and raw messages buffered on the run events handler to the run's + /// event handler. On the classic path the datacollector's messages are relayed to the console live; on + /// this path there is no live pump, so we drain the buffers at the points where new messages have + /// arrived (data collector startup and after the run completes). Both the human-readable log messages + /// and the raw (e.g. telemetry) messages are surfaced and cleared, mirroring + /// on the classic path. + /// + private void SurfaceDataCollectionMessages(IInternalTestRunEventsHandler eventHandler) + { + if (_dataCollectionEventsHandler is null) + { + return; + } + + if (_dataCollectionEventsHandler.Messages.Count > 0) { - eventHandler.HandleLogMessage(message.Item1, message.Item2); + foreach (Tuple message in _dataCollectionEventsHandler.Messages) + { + eventHandler.HandleLogMessage(message.Item1, message.Item2); + } + + _dataCollectionEventsHandler.Messages.Clear(); } - _dataCollectionEventsHandler.Messages.Clear(); + if (_dataCollectionEventsHandler.RawMessages.Count > 0) + { + foreach (string rawMessage in _dataCollectionEventsHandler.RawMessages) + { + eventHandler.HandleRawMessage(rawMessage); + } + + _dataCollectionEventsHandler.RawMessages.Clear(); + } } /// diff --git a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MtpUnderVstestTests.cs b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MtpUnderVstestTests.cs index 4476fcc41a..f03c423e8e 100644 --- a/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MtpUnderVstestTests.cs +++ b/test/Microsoft.TestPlatform.Acceptance.IntegrationTests/MtpUnderVstestTests.cs @@ -1,8 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.Generic; using System.IO; +using System.Linq; using Microsoft.TestPlatform.TestUtilities; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -19,7 +21,7 @@ namespace Microsoft.TestPlatform.AcceptanceTests; [TestClass] public class MtpUnderVstestTests : AcceptanceTestBase { - // MtpMSTestProject is an MSTest project built as an MTP application (EnableMSTestRunner): two tests + // MtpMSTestProject is an MSTest project built as an MTP application (EnableMSTestRunner): three tests // pass, one fails, one is skipped. private const string MtpApp = "MtpMSTestProject.dll"; @@ -187,7 +189,8 @@ public void RunMtpApplicationSurfacesPerTestStandardOutput(RunnerInfo runnerInfo InvokeVsTest(arguments); - ValidateSummaryStatus(2, 1, 1); + // MtpMSTestProject has five test cases: three pass, one fails, one is skipped. + ValidateSummaryStatus(3, 1, 1); var trxPath = Path.Combine(TempDirectory.Path, trxFileName); Assert.IsTrue(File.Exists(trxPath), "Expected a TRX at '{0}'.", trxPath); @@ -195,4 +198,67 @@ public void RunMtpApplicationSurfacesPerTestStandardOutput(RunnerInfo runnerInfo Assert.Contains("MTP_STDOUT_MARKER", trx, "Expected the test's standard output to be surfaced into the TRX."); Assert.Contains("MTP_STDERR_MARKER", trx, "Expected the test's standard error to be surfaced into the TRX."); } + + [TestMethod] + // A generic out-of-process data collector (SampleDataCollector) subscribes to per-test-case + // start/end events, emits per-test-case attachments and reports the launched test-host PID. In the + // classic path the testhost drives all of that; under MTP there is no testhost, so vstest.console + // owns the datacollector lifecycle and forwards the per-test-case events itself. Blame is the + // most visible collector that needs this, but the wiring must work for ANY out-of-process + // collector (Blame, Event Log, custom ones). This guards that a generic collector on the MTP path + // completes the run (no shutdown hang), reports the collector lifecycle (SessionStarted, + // TestHostLaunched, per-test-case events, SessionEnded) and produces its attachments. + [TestMatrix(testHost: Target.Net)] + public void RunMtpApplicationWithGenericOutOfProcDataCollectorCompletesRun(RunnerInfo runnerInfo) + { + SetTestEnvironment(_testEnvironment, runnerInfo); + + var extensionsPath = Path.GetDirectoryName(GetTestDllForFramework("OutOfProcDataCollector.dll", "netstandard2.0")); + var arguments = PrepareArguments( + GetAssetFullPath(MtpApp), + testAdapterPath: null, + runSettings: string.Empty, + FrameworkArgValue, + runnerInfo.InIsolationValue, + resultsDirectory: TempDirectory.Path); + arguments = string.Concat(arguments, " /Collect:SampleDataCollector", $" /TestAdapterPath:{extensionsPath}"); + + // The collector writes its per-test-case source files here and then hands them to the sink with + // deleteFile:true, so the sink moves them into the results directory as attachments. Keep this + // source directory separate from the results directory so a leftover source file (e.g. if the sink + // ever failed to delete it) cannot be mistaken for a produced attachment. + var collectorSourceDirectory = Path.Combine(TempDirectory.Path, "collector-source"); + Directory.CreateDirectory(collectorSourceDirectory); + var env = new Dictionary + { + ["TEST_ASSET_SAMPLE_COLLECTOR_PATH"] = collectorSourceDirectory, + }; + + InvokeVsTest(arguments, env); + + // The run must complete with the usual summary rather than hang at shutdown. MtpMSTestProject has + // five test cases: three pass, one fails, one is skipped. + ValidateSummaryStatus(3, 1, 1); + + // The datacollector lifecycle must be driven end to end even though there is no testhost: the + // session events, the launched-process notification and the forwarded per-test-case events all + // have to reach the out-of-process collector. Match the full datacollector message prefix so the + // assertions cannot be satisfied by unrelated console output. + StdOutputContains("Data collector 'SampleDataCollector' message: SessionStarted"); + StdOutputContains("Data collector 'SampleDataCollector' message: TestHostLaunched"); + StdOutputContains("Data collector 'SampleDataCollector' message: SessionEnded"); + StdOutputContains("Data collector 'SampleDataCollector' message: TestCaseStarted"); + StdOutputContains("Data collector 'SampleDataCollector' message: TestCaseEnded"); + + // The collector emits one attachment per started test case through the forwarded TestCaseStart + // events. All five MtpMSTestProject test cases surface a start on this path (the skipped one still + // reports a TestCaseStart), so five attachments must land in the results directory. Exclude the + // collector's own source directory so only the moved attachments are counted. + var collectorSourceDirectoryPrefix = collectorSourceDirectory + Path.DirectorySeparatorChar; + var testCaseAttachments = Directory + .GetFiles(TempDirectory.Path, "testcasefilename*.txt", SearchOption.AllDirectories) + .Where(file => !file.StartsWith(collectorSourceDirectoryPrefix, StringComparison.OrdinalIgnoreCase)) + .ToList(); + Assert.HasCount(5, testCaseAttachments, "Expected one per-test-case attachment for each started MtpMSTestProject test case forwarded on the MTP path."); + } } From 1b160f960cf76cfd095d238f9484e4111b98a43a Mon Sep 17 00:00:00 2001 From: Azat Mukhametshin Date: Wed, 15 Jul 2026 19:37:18 +0200 Subject: [PATCH 69/87] Enrich filter escaping, runsettings link, coverage, and CLI reference docs (#16277) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Docs: enrich filter escaping, runsettings link, coverage, CLI links - filter.md: shell (backslash-bang) and comma-in-generics escaping guidance. - RunSettingsArguments.md: replace dead MSDN link with the Learn runsettings reference. - analyze.md: modernize code coverage (dotnet test --collect / XPlat), keep the legacy VS2017 setup as a labeled subsection, add an Azure Pipelines VSTest task pointer. - README.md: add CLI options reference links. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4e840b63-ad95-40c2-9ef8-5f2fcc4cfdaa * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Remove docs about end-of-life products from code coverage guidance Addresses review feedback: drop the VS2017 15.3.0 legacy version note and the netcoreapp1.1 / Visual Studio 2017 'Setup a project' section, since both cover end-of-life products. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f621fca5-6520-4180-ab3b-170eda8c822b --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Jakub Jareš --- README.md | 1 + docs/RunSettingsArguments.md | 4 +-- docs/analyze.md | 47 +++++++++++------------------------- docs/filter.md | 14 +++++++++++ 4 files changed, 31 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 2043eccd9a..48214f27ce 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ NOTE: When adding a new public API, always add it directly to the `PublicAPI.Shi - [Environment Variables](./docs/environment-variables.md) - [Roadmap](./docs/releases.md) - [Troubleshooting guide](./docs/troubleshooting.md) +- Command-line options reference: [vstest.console.exe options](https://learn.microsoft.com/visualstudio/test/vstest-console-options) and [dotnet test options](https://learn.microsoft.com/dotnet/core/tools/dotnet-test) ## Building diff --git a/docs/RunSettingsArguments.md b/docs/RunSettingsArguments.md index 3a0e5d1554..c7d7af1610 100644 --- a/docs/RunSettingsArguments.md +++ b/docs/RunSettingsArguments.md @@ -36,9 +36,9 @@ where `additionalargs.runsettings` is: ``` -The syntax in (1) is another way of passing runsettings configuration and you need not author a runsetting file while using `Runsettings arguments`. More details about runsettings can be found [here](https://msdn.microsoft.com/library/jj635153.aspx). +The syntax in (1) is another way of passing runsettings configuration and you need not author a `.runsettings` file while using `RunSettings arguments`. More details about runsettings can be found [here](https://learn.microsoft.com/visualstudio/test/configure-unit-tests-by-using-a-dot-runsettings-file). -`Runsettings arguments` takes precedence over `runsettings`. +`RunSettings arguments` takes precedence over `runsettings`. For example, in below command the final value for `MapInconclusiveToFailed` will be `False` and value for `DeploymentEnabled` will be unchanged, that is `False`. diff --git a/docs/analyze.md b/docs/analyze.md index f5c60f2117..94d4f2771e 100644 --- a/docs/analyze.md +++ b/docs/analyze.md @@ -160,54 +160,35 @@ In TPv2, DataCollectors are loaded from `TestAdaptersPaths` specified in runSett ## Working with Code Coverage -> **Requirements:** -> Code Coverage requires the machine to have Visual Studio 2017 Enterprise ([15.3.0](https://www.visualstudio.com/vs) or later installed and a Windows operating system. +Code coverage can be collected from the command line with `dotnet test` or from Visual Studio Test Explorer. For current .NET projects, choose the collector that matches your platform and report format needs: -### Setup a project - -Here's a sample project file, please note the xml entity marked as `Required`. Previously, the `Microsoft.VisualStudio.CodeCoverage` was required, but is now shipped with the SDK. - -```xml - - - - netcoreapp1.1 - - - Full - - - - - - - - - -``` +- `dotnet test --collect "Code Coverage"` uses the built-in Visual Studio code coverage collector. It produces Visual Studio coverage output and is supported on Windows. +- `dotnet test --collect "XPlat Code Coverage"` uses the cross-platform Coverlet collector (requires the `coverlet.collector` NuGet package). It works on Windows, Linux, and macOS and produces coverage files such as Cobertura XML. +For complete command-line examples, package requirements, report generation, and customization options, see [Unit testing code coverage for .NET](https://learn.microsoft.com/dotnet/core/testing/unit-testing-code-coverage). ### Analyze coverage with Visual Studio -> **Version note:** -> -> Try this feature with [Visual Studio 2017 15.3.0](https://www.visualstudio.com/vs) or later. - -Use the `Analyze Code Coverage` context menu available in `Test Explorer` tool window to start a coverage run. +Use the `Analyze Code Coverage` context menu available in the `Test Explorer` tool window to start a coverage run. After the coverage run is complete, a detailed report will be available in the `Code Coverage Results` tool window. -Please refer the documentation for additional details: +For Visual Studio-specific details, see [Use code coverage to determine how much code is being tested](https://learn.microsoft.com/visualstudio/test/using-code-coverage-to-determine-how-much-code-is-being-tested). ### Collect coverage with command line runner -Use the following command line to collect coverage data for tests: +Use one of the following commands to collect coverage data for tests: ```shell -> "%vsinstalldir%\Common7\IDE\Extensions\TestPlatform\vstest.console.exe" --collect:"Code Coverage" --framework:".NETCoreApp,Version=v1.1" d:\testproject\bin\Debug\netcoreapp1.1\testproject.dll +dotnet test --collect "Code Coverage" +dotnet test --collect "XPlat Code Coverage" ``` -This will generate a `*.coverage` file in the `\TestResults` directory. +### Collect coverage in Azure Pipelines + +In Azure DevOps pipelines you can run tests and collect coverage with the Visual Studio Test task, which wraps `vstest.console.exe`. Enable coverage via the task's `codeCoverageEnabled` input (or pass `/collect:"Code Coverage"` through `otherConsoleOptions`). See [VSTest@2 - Visual Studio Test task](https://learn.microsoft.com/azure/devops/pipelines/tasks/reference/vstest-v2). + +Coverage attachments are written under the test run's `TestResults` directory. ### Event Log Data Collector diff --git a/docs/filter.md b/docs/filter.md index 9cd974f300..560e3dd972 100644 --- a/docs/filter.md +++ b/docs/filter.md @@ -47,6 +47,20 @@ Allowed **operators**: A helper method `Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities.FilterHelper.Escape` is also available by referencing the `Microsoft.VisualStudio.TestPlatform.ObjectModel` NuGet package, which can be used to escape strings programatically. +### Shell escaping + +The filter expression is parsed by both your shell and the test platform, so some characters may need shell-specific escaping before VSTest receives them. + +On Linux and macOS shells, escape `!` with a backslash when using the `!~` operator: + +```shell +dotnet test --filter FullyQualifiedName\!~IntegrationTests +``` + +If a `FullyQualifiedName` value contains characters with special meaning to your shell (for example `<`, `>`, or `,` in a generic type argument list), quote the filter expression so it is passed through literally (required in PowerShell, where `,` is the array operator): + + dotnet test --filter "FullyQualifiedName=MyNamespace.MyTestsClass.MyTestMethod" + Expressions can be joined with boolean operators. The following boolean operators are supported: * `|` implies a boolean `OR` From 821d5b602761c06ee420cb0e826f7a5ea9db495d Mon Sep 17 00:00:00 2001 From: Azat Mukhametshin Date: Wed, 15 Jul 2026 22:07:20 +0200 Subject: [PATCH 70/87] Modernize stale docs (TFMs, code coverage, env vars, MTP context) (#16279) --- docs/Overview.md | 10 ++- docs/configure.md | 39 ++++++---- docs/dotnetcoretests.md | 29 +++---- docs/environment-variables.md | 19 ++--- docs/extensions/datacollector-migration.md | 2 +- docs/extensions/datacollector.md | 86 ++++++++++++++------- docs/quickstart.md | 9 +++ docs/report.md | 2 +- docs/testplatform-migration-known-issues.md | 6 +- 9 files changed, 124 insertions(+), 78 deletions(-) diff --git a/docs/Overview.md b/docs/Overview.md index b75d2a68b8..621d3f3147 100644 --- a/docs/Overview.md +++ b/docs/Overview.md @@ -70,7 +70,7 @@ TestPlatform is also known as vstest, or by the names of the tools that use it: ## How it works? -TestPlatform consists of multiple processes that communicate over sockets, by sending JSON serialized messages. There are 4 processes that usually work together run tests: +TestPlatform consists of multiple components that communicate by sending JSON serialized messages. A classic VSTest run usually involves these processes: - Client - Runner @@ -83,7 +83,9 @@ The runner receives the request from the client, and starts an appropriate testh Testhost receives the request to run tests, and runs them via an appropriate test framework. The most often used .NET test frameworks are XUnit, MSTest and NUnit. -Datacollector observes the testhost to collect additional information about the run. +Datacollector observes the testhost to collect additional information about the run when data collection is enabled. + +Microsoft.Testing.Platform (MTP) test applications are an emerging model. For those applications, the application hosts itself and TestPlatform drives discovery and execution over the MTP protocol instead of launching a VSTest testhost. While the tests execute, the results are reported back to the runner, aggregated, and forwarded to the client. @@ -248,7 +250,7 @@ The version is negotiated between the components at the beginning of every workf #### Request, Notification and Response Ordering -The server supports processing only a single request at a time. Unless the request is [Cancel](#cancel) or [Abort](#abort) request. +The server supports processing only a single request at a time, unless the request is a Cancel or Abort request. All notifications are sent before a response is sent. @@ -293,7 +295,7 @@ The version is determined by choosing the highest common supported version. When The receiving side should remember the agreed value, and use it as the highest supported version for any downstream component. In the case above runner should send 6 to testhost, even though the runner supports versions up to 7. -The request was introduced in TestPlatform version `16.0.0`. Runners before this version are not allowed. Testhosts before this version are allowed, the version of testhost is figured out by scanning the assembly, and the request is not sent to them. Version 0 is used for communication. +The current source defines `Version0` as the lowest supported protocol version and `Version7` as the highest supported protocol version. Versions: diff --git a/docs/configure.md b/docs/configure.md index 5af8f0696e..892326a7ae 100644 --- a/docs/configure.md +++ b/docs/configure.md @@ -8,8 +8,8 @@ There are three different ways to configure various aspects of a test run. 1. **Using command line arguments** Various configuration options can be provided to the `vstest.console` or `dotnet -test` command line. For example, `--framework` can specify the runtime framework -version, or `--platform` can specify the architecture of test run (`x86` or +test` command line. For example, `--framework` can specify the target framework, +or `--platform` can specify the architecture of test run (`x86` or `x64`). 1. **Using a runsettings file** @@ -61,6 +61,10 @@ The `runsettings` file is a xml file with following sections: 1. Adapter Configuration 1. Legacy Settings +TestSettings (`*.testsettings`) are deprecated. Prefer `*.runsettings`; legacy settings +are supported only for MSTest v1 and ordered-test scenarios that explicitly opt into +legacy mode. + We will cover these sections in detail later in the document. Let's discuss few core principles for runsettings. @@ -93,8 +97,8 @@ document. x86 - - Framework40 + + net8.0 %SystemDrive%\Temp\foo;%SystemDrive%\Temp\bar @@ -109,7 +113,7 @@ document. 10000 - + STA @@ -150,7 +154,7 @@ document. - + 4312 @@ -209,7 +213,7 @@ _Example_ x86 - .NET Framework, Version=v4.6 + net8.0 %SystemDrive%\Temp\foo;%SystemDrive%\Temp\bar .\TestResults .\TestResults @@ -230,16 +234,19 @@ _Description_ | ResultsDirectory | string | Directory for test run reports. E.g. trx, coverage etc. | | SolutionDirectory | string | Working directory for test invocation. Results directory can be relative to this. Used by IDEs. | | MaxCpuCount | int | Degree of parallelization, spawns `n` test hosts to run tests. Default: 1. Max: Number of cpu cores. | -| TestSessionTimeout | int | Testplatform will cancel the test run after it exceeded given TestSessionTimeout in milliseconds and will show the results of tests which ran till that point. **Required Version: 15.5+.** | -| ExecutionThreadApartmentState | string | Apartment state of thread which calls adapter's RunTests and Cancel APIs. Possible values: (MTA, STA). default is STA for .NET Full and MTA for .NET Core. STA supported only for .NET Full **Required Version: 15.5+.** [More details.](#execution-thread-apartment-state) | +| TestSessionTimeout | int | Test Platform will cancel the test run after it exceeded given TestSessionTimeout in milliseconds and will show the results of tests which ran till that point. | +| ExecutionThreadApartmentState | string | Apartment state of thread which calls adapter's RunTests and Cancel APIs. Possible values: (MTA, STA). Default is STA for .NET Framework and MTA for .NET. STA is supported only for .NET Framework. [More details.](#execution-thread-apartment-state) | Examples of valid `TargetFrameworkVersion`: -* .NETCoreApp, Version=v1.0 -* .NETCoreApp, Version=v1.1 -* .NETFramework, Version=v4.5 +* net462 +* net8.0 +* net9.0 +* net10.0 +* .NETFramework,Version=v4.8 +* .NETCoreApp,Version=v8.0 -[FrameworkName]: https://msdn.microsoft.com/en-us/library/dd414023(v=vs.110).aspx +[FrameworkName]: https://learn.microsoft.com/dotnet/standard/frameworks 2. **Adapter settings** These settings are a hint to adapters to behave in a particular way. These are @@ -482,7 +489,7 @@ This section explains usage of ExecutionThreadApartmentState element in runsetti vstest.console.exe a.dll -- RunConfiguration.ExecutionThreadApartmentState=STA -dotnet test -f net46 -- RunConfiguration.ExecutionThreadApartmentState=STA +dotnet test -f net462 -- RunConfiguration.ExecutionThreadApartmentState=STA ### History @@ -490,8 +497,8 @@ In Test Platform V1 ExecutionThreadApartmentState property can be set from vstes ### Behavior -In Test platform V2 ExecutionThreadApartmentState property default value is `MTA` for .NET Core and `STA` for .NET Full. `STA` value is only supported for .NET Framework. -Warning should be shown on trying to set value `STA` for .NET Core and UAP10.0 frameworks tests. +In Test Platform V2 ExecutionThreadApartmentState property default value is `MTA` for .NET and `STA` for .NET Framework. `STA` value is only supported for .NET Framework. +Warning should be shown on trying to set value `STA` for .NET and UAP10.0 framework tests. * To support adapters which depends on thread test platform creates may need STA apartment state to run UI tests. `ExecutionThreadApartmentState` option can be used to set apartment state. Example: MSTest v1, MSTest v2 and MSCPPTest adapters. diff --git a/docs/dotnetcoretests.md b/docs/dotnetcoretests.md index 1aebadbb2c..9ec916201d 100644 --- a/docs/dotnetcoretests.md +++ b/docs/dotnetcoretests.md @@ -1,17 +1,18 @@ -For dotnet core test projects, the test platform is acquired as a nuget package (Microsoft.NET.Test.Sdk) and the runtime (testhost.dll similar to testhost.exe) is also part of the nuget package. +For SDK-style .NET test projects, the supported path is to reference +`Microsoft.NET.Test.Sdk` and run tests with `dotnet test`. -When dotnet build runs, the above mentioned packages are restored to the users' global nuget cache in the absence of any overridden config. When vstest.console.exe runs .runtimeconfig.dev.json is looked into to determine the folders to look for the testhost.dll (something like the below) in addition to the folder where the test dll is present (https://github.com/Microsoft/vstest/blob/c7472a479966a218fb0ac508ed799418eb4bfc00/src/Microsoft.TestPlatform.TestHostProvider/Hosting/DotnetTestHostManager.cs#L374) +`dotnet test` builds the test project, restores NuGet packages, and runs the test +assembly with the test platform components provided by `Microsoft.NET.Test.Sdk`. +The VSTest testhost provider still uses the test assembly's `.deps.json` and, when +present, `.runtimeconfig.dev.json` to locate `testhost.dll`; if it cannot resolve +the package-provided testhost for a managed test assembly, it fails with guidance +to add `Microsoft.NET.Test.Sdk`. -{ - "runtimeOptions": { - "additionalProbingPaths": [ - "C:\\Users\\shivash\\.dotnet\\store\\|arch|\\|tfm|", - "C:\\Users\\shivash\\.nuget\\packages", - "C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder" - ] - } -} +In normal SDK-style projects there is no separate VSTest-specific probing-path setup +to document or configure. If you need to run tests from a copied directory or on a +machine that does not have the restored NuGet packages, publish the test project and +run from the publish output so the test assembly and its runtime dependencies are +available together. -In the case of the customer, the nuget packages aren't being restored to the paths defined in the .runtimeconfig.dev.json resulting in the testhost.dll not being determined. The locations used for the nuget packages can be determined using the below : https://learn.microsoft.com/en-us/nuget/consume-packages/managing-the-global-packages-and-cache-folders#viewing-folder-locations - -To mitigate the issue and as a general recommendation for running dot net core tests with vstest task please ask the customer to publish the test project and point to the publish location for running tests. Publish ensures all needed dependencies are present for the tests to be executed alongside the test dll in case of dot net core tests. +Current `dotnet test` usage is documented in the .NET testing guide: +. diff --git a/docs/environment-variables.md b/docs/environment-variables.md index 0809ac93f2..828da79b93 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -1,6 +1,6 @@ # VSTest Environment Variables -This document lists all environment variables that are understood and handled by the Visual Studio Test Platform (VSTest). These variables can be used to configure various aspects of test execution, debugging, diagnostics, and feature behavior. +This document lists environment variables that are currently handled by VSTest source code, plus a few historical variables that are called out as removed or obsolete. It is not an exhaustive list of every variable used by every adapter or hosting environment. ## Connection and Timeout Variables @@ -31,7 +31,8 @@ This document lists all environment variables that are understood and handled by - **Example**: `VSTEST_DIAG_VERBOSITY=Info` ### VSTEST_LOGFOLDER -- **Description**: Specifies the folder where test logs should be written. +- **Status**: Not used by product code under `src/`; referenced only in `test/` assets. +- **Previous description**: Specified the folder where test logs should be written. - **Example**: `VSTEST_LOGFOLDER=C:\TestLogs` ## Debug Variables @@ -91,7 +92,7 @@ This document lists all environment variables that are understood and handled by - **Example**: `VSTEST_DEBUG_ATTACHVS_PATH=C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\devenv.exe` ### VSTEST_DEBUG_NOBP -- **Description**: Disables breakpoints on executable entry points, to for more seemless debugging when using AttachVS. +- **Description**: Disables breakpoints on executable entry points for more seamless debugging when using AttachVS. - **Values**: Set to "1" to disable breakpoints - **Example**: `VSTEST_DEBUG_NOBP=1` @@ -130,14 +131,12 @@ This document lists all environment variables that are understood and handled by - **Description**: Disables artifact post-processing functionality. - **Values**: Set to any non-zero value to disable - **Example**: `VSTEST_DISABLE_ARTIFACTS_POSTPROCESSING=1` -- **Added**: Version 17.2-preview, 7.0-preview ### VSTEST_DISABLE_ARTIFACTS_POSTPROCESSING_NEW_SDK_UX - **Description**: Disables new SDK UX for artifact post-processing, showing old output format. - **Values**: Set to any non-zero value to disable - **Example**: `VSTEST_DISABLE_ARTIFACTS_POSTPROCESSING_NEW_SDK_UX=1` - **Usage**: Useful when parsing console output and need to maintain compatibility -- **Added**: Version 17.2-preview, 7.0-preview ### VSTEST_DISABLE_FASTER_JSON_SERIALIZATION - **Description**: Disables the faster JSON serialization mechanism and falls back to standard serialization. @@ -215,7 +214,7 @@ This document lists all environment variables that are understood and handled by ## Configuration and Path Variables ### VSTEST_CONSOLE_PATH -- **Description**: Specifies the path to the vstest.console executable. +- **Description**: Specifies the path to the vstest.console executable. This variable is read by the .NET SDK's `dotnet test` forwarding app (not by vstest product code under `src/` in this repo), which is why there are no `src/` references here. It is equivalent to specifying `-p:VSTestConsolePath` when using `dotnet test` with a project. - **Example**: `VSTEST_CONSOLE_PATH=C:\Tools\VSTest\vstest.console.exe` ### VSTEST_IGNORE_DOTNET_ROOT @@ -242,17 +241,11 @@ This document lists all environment variables that are understood and handled by ## Windows App Host Variables ### VSTEST_WINAPPHOST_* -- **Description**: Various environment variables related to Windows App Host configuration. +- **Description**: Prefix historically read by `DotnetTestHostManager` when launching the custom Windows app host (`testhost.exe`). The .NET SDK set `VSTEST_WINAPPHOST_DOTNET_ROOT` / `VSTEST_WINAPPHOST_DOTNET_ROOT(x86)` to support private-install scenarios; vstest forwarded them (stripping the prefix) as `DOTNET_ROOT` / `DOTNET_ROOT(x86)` so the app host could locate the runtime. Here "app host" means the native `testhost.exe` launcher — this is **not** related to the Windows App SDK, UWP, or .NET MAUI. This mechanism existed in older versions (up to ~17.7); it is **not** referenced anywhere under this repository's current `src/`, because current source sets the standard `DOTNET_ROOT` / `DOTNET_ROOT(x86)` / `DOTNET_ROOT_` variables directly. - **Pattern**: Variables following the pattern `VSTEST_WINAPPHOST_{VARIABLE_NAME}` -- **Usage**: Used internally for Windows App Host test execution scenarios ## Legacy/Experimental Variables -### VSTEST_EXPERIMENTAL_FORWARD_OUTPUT_FEATURE -- **Description**: (Deprecated) Previously used to enable output forwarding feature. -- **Status**: Replaced by VSTEST_DISABLE_STANDARD_OUTPUT_CAPTURING and VSTEST_DISABLE_STANDARD_OUTPUT_FORWARDING -- **Note**: This variable is no longer used as the feature is now enabled by default - ### VSTEST_DISABLE_PROTOCOL_3_VERSION_DOWNGRADE - **Description**: Disables automatic downgrade to protocol version 3 for compatibility. - **Values**: Set to any non-empty value to disable downgrade diff --git a/docs/extensions/datacollector-migration.md b/docs/extensions/datacollector-migration.md index 1b0bcf1e7c..fdea8319ac 100644 --- a/docs/extensions/datacollector-migration.md +++ b/docs/extensions/datacollector-migration.md @@ -5,7 +5,7 @@ This document will walk you through the changes that are required to migrate you ## Referencing DataCollector Framework Previously, `DataCollector` abstract class was present in `Microsoft.VisualStudio.QualityTools.ExecutionCommon.dll` under namespace `Microsoft.VisualStudio.TestTools.Execution`. -Now, `DataCollector`abstract class is present in Object Model. Add reference to [`Microsoft.TestPlatform.ObjectModel`](https://www.nuget.org/packages/Microsoft.TestPlatform.ObjectModel/15.5.0-preview-20170810-02) (preview) nuget package. DataCollector APIs are present under namespace `Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection`. +Now, `DataCollector` abstract class is present in Object Model. Add reference to [`Microsoft.TestPlatform.ObjectModel`](https://www.nuget.org/packages/Microsoft.TestPlatform.ObjectModel) NuGet package. DataCollector APIs are present under namespace `Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection`. It is recommended to target your DataCollector to netstandard, so that it can also run cross-platform, i.e. on non-Windows operating systems. For more info, refer to this [guide](./datacollector.md). diff --git a/docs/extensions/datacollector.md b/docs/extensions/datacollector.md index 67aeabfddf..b0cb053796 100644 --- a/docs/extensions/datacollector.md +++ b/docs/extensions/datacollector.md @@ -5,7 +5,7 @@ In this walkthrough, you will learn how to create your first `DataCollector` and ## Extend DataCollector The very first thing you will need to create is a Class Library project and add reference to `Microsoft.TestPlatform.ObjectModel` nuget package. -Class Library project can target Desktop clr or dotnet core clr or both frameworks. +Class Library project can target .NET Framework (for example `net462`) or .NET (for example `net8.0`, `net9.0`, or `net10.0`), or multi-target both families when the collector needs to run in both environments. > **DataCollector Assembly Naming Convention** > @@ -19,39 +19,52 @@ Class Library project can target Desktop clr or dotnet core clr or both framewor A new data collector can be implemented by extending the abstract `DataCollector` class. ```csharp +using System; +using System.IO; +using System.Xml; + using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; [DataCollectorFriendlyName("NewDataCollector")] [DataCollectorTypeUri("my://new/datacollector")] public class NewDataCollector : DataCollector { - private string logFileName; - private DataCollectionEnvironmentContext context; + private string logFileName = "DataCollectorLogs.txt"; + private DataCollectionEnvironmentContext? context; + private DataCollectionSink dataSink = null!; + private DataCollectionLogger logger = null!; public override void Initialize( - System.Xml.XmlElement configurationElement, + XmlElement? configurationElement, DataCollectionEvents events, DataCollectionSink dataSink, DataCollectionLogger logger, - DataCollectionEnvironmentContext environmentContext) + DataCollectionEnvironmentContext? environmentContext) { + this.context = environmentContext; + this.dataSink = dataSink; + this.logger = logger; + events.SessionStart += this.SessionStarted_Handler; events.TestCaseStart += this.Events_TestCaseStart; - logFileName = configurationElement["LogFileName"]; + logFileName = configurationElement?["LogFileName"]?.InnerText ?? logFileName; } - - private void SessionStarted_Handler(object sender, SessionStartEventArgs args) + + private void SessionStarted_Handler(object? sender, SessionStartEventArgs args) { var filename = Path.Combine(AppContext.BaseDirectory, logFileName); File.WriteAllText(filename, "SessionStarted"); - this.dataCollectionSink.SendFileAsync(this.context.SessionDataCollectionContext, filename, true); - this.logger.LogWarning(this.context.SessionDataCollectionContext, "SessionStarted"); - } + if (context is not null) + { + dataSink.SendFileAsync(context.SessionDataCollectionContext, filename, true); + logger.LogWarning(context.SessionDataCollectionContext, "SessionStarted"); + } + } - private void Events_TestCaseStart(object sender, TestCaseStartEventArgs e) + private void Events_TestCaseStart(object? sender, TestCaseStartEventArgs e) { - this.logger.LogWarning(this.context.SessionDataCollectionContext, "TestCaseStarted " + e.TestCaseName); + logger.LogWarning(e.Context, "TestCaseStarted " + e.TestCaseName); } } ``` @@ -77,9 +90,11 @@ For supporting those scenarios, configuration xml can be passed to DataCollector ``` ```csharp -XmlElement logFileElement = configurationElement[LogFileName]; -string logFile = logFileElement != null ? logFileElement.InnerText : string.Empty; -if (!File.Exists(logFile)) +XmlElement? logFileElement = configurationElement?["LogFileName"]; +string logFile = logFileElement?.InnerText ?? "DataCollectorLogs.txt"; +string path = Path.Combine(AppContext.BaseDirectory, logFile); + +if (!File.Exists(path)) { // Create a file to write to. string createText = "Hello and Welcome" + Environment.NewLine; @@ -93,14 +108,14 @@ DataCollectors can choose to subscribe to the following events exposed by `DataC 2. TestSessionEnd : Raised when test execution session ends. 3. TestCaseStart : Raised when test case execution starts. 4. TestCaseEnd : Raised when test case execution ends. -5. TestHostLaunched : Raised when test host process has been initialized. **Note: This will be available from 15.7** +5. TestHostLaunched : Raised when test host process has been initialized. ```csharp events.SessionStart += this.SessionStarted_Handler; events.SessionEnd += this.SessionEnded_Handler; events.TestCaseStart += this.Events_TestCaseStart; events.TestCaseEnd += this.Events_TestCaseEnd; -events.TestHostLaunched += this.TestHostLaunched_Handler +events.TestHostLaunched += this.TestHostLaunched_Handler; ``` ```csharp private void Events_TestCaseStart(object sender, TestCaseStartEventArgs e) @@ -117,25 +132,33 @@ dataSink.SendFileAsync(context, filename, true); Files sent using above api get associated with session level attachments or test case level attachments based on the context passed. ### DataCollectionEnvironmentContext -DataCollector framework maintains a session level context for test exectuion session and test level contexts for each test that gets executed. +DataCollector framework maintains a session level context for test execution session and test level contexts for each test that gets executed. `DataCollectionEnvironmentContext` passed as argument in constructor has session level context that can be accessed through property `SessionDataCollectionContext`. Test case level context can be accessed through `TestCaseStartEventArgs.Context` or `TestCaseEndEventArgs.Context`. ```csharp private void Events_TestCaseStart(object sender, TestCaseStartEventArgs e) { - // Session level attachment - this.dataCollectionSink.SendFileAsync(this.context.SessionDataCollectionContext, filename, true); + // Session level attachment. environmentContext can be null, so guard before using it. + if (this.context is not null) + { + this.dataSink.SendFileAsync(this.context.SessionDataCollectionContext, filename, true); + } + // TestCase level attachment - this.dataCollectionSink.SendFileAsync(e.Context, filename, true); + this.dataSink.SendFileAsync(e.Context, filename, true); } ``` ### DataCollectionLogger DataCollectors can also log errors or warnings using `DataCollectionLogger`. ```csharp -logger.LogError(this.context.SessionDataCollectionContext, new Exception("my exception")); -logger.LogWarning(this.context.SessionDataCollectionContext, "my warning"); +// environmentContext can be null, so guard before using the session context. +if (this.context is not null) +{ + logger.LogError(this.context.SessionDataCollectionContext, new Exception("my exception")); + logger.LogWarning(this.context.SessionDataCollectionContext, "my warning"); +} ``` ### DataCollection Environment Variables @@ -143,24 +166,35 @@ DataCollectors can choose to specify information about how the test execution en E.g. setting up the Environment Variables required by profiler engine for code coverage. ```csharp +using System.Collections.Generic; + [DataCollectorFriendlyName("NewDataCollector")] [DataCollectorTypeUri("my://new/datacollector")] class NewDataCollector : DataCollector, ITestExecutionEnvironmentSpecifier { public IEnumerable> GetTestExecutionEnvironmentVariables() { + return new[] { new KeyValuePair("MY_PROFILER_SETTING", "1") }; } } ``` -Environment variables returned by the above method are set in the test execution process while bootstraping. +Environment variables returned by the above method are set in the test execution process while bootstrapping. ## Using DataCollector Once the DataCollector is compiled, it can be used to monitor test execution. There are two ways by which datacollectors can be plugged in: 1. Using /collect switch : `vstest.console.exe /collect: /testadapterpath: /testadapterpath:` +The equivalent `dotnet test` command uses `--collect` and passes adapter paths through runsettings: +`dotnet test --collect:"" -- RunConfiguration.TestAdaptersPaths=` + 2. Using runsettings : -`vstest.console.exe /settings: +`vstest.console.exe /settings:` + +or + +`dotnet test --settings ` + ```xml diff --git a/docs/quickstart.md b/docs/quickstart.md index 47bf600165..67ca4c2f42 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -1 +1,10 @@ # Quickstart Guide + +New to running .NET tests with the test platform? Start with the official .NET testing documentation: + +- [Testing in .NET](https://learn.microsoft.com/dotnet/core/testing/) — overview and getting started +- [Unit testing with `dotnet test`](https://learn.microsoft.com/dotnet/core/testing/unit-testing-with-dotnet-test) +- [`dotnet test` command reference](https://learn.microsoft.com/dotnet/core/tools/dotnet-test) +- [`vstest.console.exe` command-line options](https://learn.microsoft.com/visualstudio/test/vstest-console-options) + +For test platform internals and extensibility, see the [Overview](./Overview.md) and the [RFCs](./RFCs). diff --git a/docs/report.md b/docs/report.md index 0dee97d6bf..78dda2bbe7 100644 --- a/docs/report.md +++ b/docs/report.md @@ -125,7 +125,7 @@ Console logger is the default logger and it is used to output the test results t #### Syntax -For dotnet test or dotnet vstest: +For dotnet test or dotnet vstest (note: [`dotnet vstest` is superseded by `dotnet test`](https://learn.microsoft.com/dotnet/core/tools/dotnet-vstest), which can run assemblies directly): ```shell --logger:console[;verbosity=] diff --git a/docs/testplatform-migration-known-issues.md b/docs/testplatform-migration-known-issues.md index ab794db1f5..5850cdceca 100644 --- a/docs/testplatform-migration-known-issues.md +++ b/docs/testplatform-migration-known-issues.md @@ -7,7 +7,7 @@ Here are the current known issues you may face when running tests, along with av - **Issue:** Tests that depend on `Thread.CurrentPrincipal` may fail. This is due to change in inter process communication in Test Platform. - **Workaround:** Use an alternative like `System.Security.Principal.WindowsIdentity.GetCurrent()` -## Change in test execution processes name +## Change in test execution process name -- **Issue:** Tests that depend on the name of the currnet running process may fail. -- **Workaround:** Tests run in one of following process ```vstest.console.exe```, ```testhost.exe```, ```testhost.x86.exe``` or ```dotnet.exe``` based on run configuration (```/Platform``` and ```/Framework```). If the tests depend on the process name, then update the tests accordingly. +- **Issue:** Tests that depend on the name of the currently running process may fail. +- **Workaround:** Tests run in one of the following processes: ```vstest.console.exe```, ```testhost.exe```, ```testhost.x86.exe``` or ```dotnet.exe``` based on run configuration (```/Platform``` and ```/Framework```). If the tests depend on the process name, then update the tests accordingly. From 4ed742de4912a38aa41eb10fd20de09088be6425 Mon Sep 17 00:00:00 2001 From: Azat Mukhametshin Date: Thu, 16 Jul 2026 13:27:47 +0200 Subject: [PATCH 71/87] Resolve TODO placeholders in Overview.md and configure.md (#16278) --- docs/Overview.md | 187 ++++++++++++++++++++--- docs/configure.md | 4 +- docs/resources/test-explorer-example.gif | Bin 0 -> 1257384 bytes 3 files changed, 169 insertions(+), 22 deletions(-) create mode 100644 docs/resources/test-explorer-example.gif diff --git a/docs/Overview.md b/docs/Overview.md index 621d3f3147..a1e1ee1912 100644 --- a/docs/Overview.md +++ b/docs/Overview.md @@ -56,6 +56,10 @@ - [Test Logger](#test-logger) - [Runtime Provider](#runtime-provider) - [TranslationLayer extension points](#translationlayer-extension-points) + - [Public surface of the TranslationLayer project](#public-surface-of-the-translationlayer-project) + - [Usage: driving the wrapper](#usage-driving-the-wrapper) + - [Extension points: interfaces you implement](#extension-points-interfaces-you-implement) + - [Configuring the runner process (ConsoleParameters)](#configuring-the-runner-process-consoleparameters) - [.NET Implementation](#net-implementation) - [Architecture](#architecture) @@ -91,7 +95,7 @@ While the tests execute, the results are reported back to the runner, aggregated The client processes the results and shows them in their UI, for example as TestExplorer does it here: - +![Visual Studio Test Explorer showing the results of a completed test run](resources/test-explorer-example.gif) A simplified flow describing the whole process is as follows: @@ -141,8 +145,6 @@ The Run workflow described above is very common in command line tools, and proba ### Base Protocol -TODO: fill in more details. - Data are passed as JSON serialized strings over TCP. The messages are serialized using binary format that delimits messages by a length prefix. The size prefix is written as 7 bit encoded int. (The basics of encoding that number are summarized here: ). In .NET the data are written using BinaryWriter and BinaryReader which do all the needed conversions automatically when writing and reading the string ()). @@ -230,10 +232,10 @@ There is no header in the message itself. There is header only in the binary mes TestPlatform protocol defines a set of JSON request, response and notification messages, that are exchanged using the above base protocol. This section starts by describing the basic JSON structures used in the protocol. The description uses C# classes, and types, with nullability enabled. Meaning that every type is non-nullable by default, and nullability is denoted by `?` following the type name. - - The protocol assumes that one server serves one tool. There is no support in the protocol to share one server between different tools. +See [Message documentation](#message-documentation) for the format used to describe every message, including a worked [ProtocolVersion request](#protocolversion-request) example. + #### Capabilities The client, runner + datacollector, and testhost are shipped separately. Each of those components can have a different version and hence a different set of functionality they support. A single number (protocol version) is used to represent the whole set of capabilities that a given component supports. Each newer version includes complete functionality of the previous version. There are no granular capabilities. @@ -258,10 +260,32 @@ All notifications are sent before a response is sent. TestPlatformProtocol is defined by a set of requests, responses and notifications. Each of those are described using the following format: -- a header describing the request -- a request section describing the format of the +- a header describing the message +- a request section describing the request payload format +- a response section describing the response payload format, when the message has a response +- examples of the JSON sent on the wire + +For example, the [ProtocolVersion request](#protocolversion-request) below follows this format. Because it is exchanged during negotiation, it uses the unversioned message envelope (the `Version` property is only emitted for protocol v1 and above) and therefore omits `Version` from the JSON examples. +Its header describes the message, the *Request* section documents the request payload as the JSON sent on +the wire, and the *Response* section documents the reply the same way: + +*Request:* + +```json +{ + "MessageType": "ProtocolVersion", + "Payload": 7 +} +``` - +*Response:* + +```json +{ + "MessageType": "ProtocolVersion", + "Payload": 7 +} +``` ### Basic structures @@ -304,7 +328,7 @@ Versions: - 2: Changed serialization from a generic bag that described each property and its type, to explicit properties that are serialized without additional type info. - 3: Added AttachDebugger message. - 4: Added because version 3 did not update the serialization to use, and it will use v1 serialization (bag) rather than explicit properties. Right side should avoid negotiating 3 and downgrade to 2. -- 5: Unknown. (TODO) +- 5: Unknown in the core `ProtocolVersioning` table (the source still marks this version as `// 5: ???`). The TranslationLayer defines a private `MinimumProtocolVersionWithTestSessionSupport = 5` in `VsTestConsoleRequestSender`, but the constant is currently unused, so the exact change associated with v5 is unclear from the current source. - 6: Added Abort and Cancel with handlers that report the status. - 7: Added SkippedDiscoveredSources. @@ -618,7 +642,9 @@ public class DiscoveryCompletePayload public IList? NotDiscoveredSources { get; set; } = new List(); // Gets or sets the collection of discovered extensions. - // TODO: since? + // Introduced as a telemetry data point in v17.2.0 (PR dotnet/vstest#3511); not gated by a + // protocol version. Can be null (for example before extension discovery has populated + // TestPluginCache), so its presence on the wire is not guaranteed. public Dictionary>? DiscoveredExtensions { get; set; } = new(); } ``` @@ -709,7 +735,7 @@ Contains full paths to one or more test sources, and settings to use for the dis ```csharp public class DiscoveryCriteria { - // Gets the test Containers (e.g. .appx, .appxrecipie) TODO what??? + // Gets the test container package path (for example, an appx package). public string? Package { get; set; } @@ -793,7 +819,9 @@ public class DiscoveryCompletePayload public IList? NotDiscoveredSources { get; set; } = new List(); // Gets or sets the collection of discovered extensions. - // TODO: since? + // Introduced as a telemetry data point in v17.2.0 (PR dotnet/vstest#3511); not gated by a + // protocol version. Can be null (for example before extension discovery has populated + // TestPluginCache), so its presence on the wire is not guaranteed. public Dictionary>? DiscoveredExtensions { get; set; } = new(); } ``` @@ -1107,8 +1135,7 @@ public class TestRunCompleteEventArgs // Error encountered in the run that is not linked to any test. public Exception? Error { get; private set; } - // Gets the attachment sets associated with the test run. - // TODO: HOW is this different from RunAttachments above? + // Gets the attachment sets associated with the test run (for example data-collector output). These are distinct from RunAttachments on the enclosing TestRunCompletePayload, which carries the run-context attachments (runContextAttachments) sent alongside ExecutionComplete. public Collection AttachmentSets { get; private set; } // Gets the invoked data collectors for the test session. @@ -1251,7 +1278,9 @@ public class TestExecutionContext // Gets or sets a value indicating whether testhost process should be kept running after test run completion. public bool KeepAlive { get; set; } - // Gets or sets a value indicating whether test case level events need to be sent or not. TODO: what is it? Is there since first commit, no usages on grep.app. + // Gets or sets a value indicating whether test case level events (TestCaseStart / TestCaseEnd) + // are required. The cross-platform execution managers currently always pass false here + // (see ProxyExecutionManager and InProcessProxyExecutionManager). public bool AreTestCaseLevelEventsRequired { get; set; } // Gets or sets a value indicating whether execution is in debug mode. @@ -1357,8 +1386,7 @@ public class TestRunCompleteEventArgs // Error encountered in the run that is not linked to any test. public Exception? Error { get; private set; } - // Gets the attachment sets associated with the test run. - // TODO: HOW is this different from RunAttachments above? + // Gets the attachment sets associated with the test run (for example data-collector output). These are distinct from RunAttachments on the enclosing TestRunCompletePayload, which carries the run-context attachments (runContextAttachments) sent alongside ExecutionComplete. public Collection AttachmentSets { get; private set; } // Gets the invoked data collectors for the test session. @@ -1858,10 +1886,129 @@ Additional example of a toy test framework and adapter can be found in