diff --git a/src/AcDream.Headless/Hosting/HeadlessProcessHost.cs b/src/AcDream.Headless/Hosting/HeadlessProcessHost.cs index f0e10b2f5..323d80152 100644 --- a/src/AcDream.Headless/Hosting/HeadlessProcessHost.cs +++ b/src/AcDream.Headless/Hosting/HeadlessProcessHost.cs @@ -192,6 +192,14 @@ internal sealed class HeadlessProcessHost : IDisposable }; console = controller; } + else if (consoleEnabled) + { + // S7 (2026-09-07 review round): a silent skip here read as + // "--console worked" to an operator with no way to tell + // otherwise — the launcher's multi-session mode is a + // legitimate, common configuration, so say so explicitly. + _diagnostics.Message("console", "single-session only"); + } _console = console; _consoleRendererSubscription = consoleRendererSubscription; } diff --git a/tests/AcDream.Headless.Tests/HeadlessConsoleTests.cs b/tests/AcDream.Headless.Tests/HeadlessConsoleTests.cs index 3aded57cf..4a0b5f45e 100644 --- a/tests/AcDream.Headless.Tests/HeadlessConsoleTests.cs +++ b/tests/AcDream.Headless.Tests/HeadlessConsoleTests.cs @@ -564,6 +564,58 @@ public sealed class HeadlessConsoleTests Assert.Equal(expectDimmed, text.Contains("[2m")); } + /// + /// S7: a multi-session process with --console must tell the + /// operator why the console never attached (the launcher's multi-session + /// mode is a legitimate, common configuration) instead of silently + /// doing nothing — see HeadlessDiagnosticWriter.Message's "console" + /// category. + /// + [Fact] + public void TwoSessionsWithConsoleFlagReportsSingleSessionOnly() + { + // StandardInput credentials (one line per session, consumed by + // HeadlessCredentialResolver BEFORE the console reader thread ever + // starts — see HeadlessProcessHost's own constructor comment) avoid + // needing an ACE-shaped Environment credential just to reach the + // console-wiring branch this test targets. + HeadlessSessionDescriptor StandardInputDescriptor(string id) => + Descriptor() with + { + Id = id, + Credential = new HeadlessCredentialReference + { + Provider = HeadlessCredentialProviderKind.StandardInput, + Reference = "fixture", + }, + }; + var configuration = new HeadlessConfiguration + { + Version = 1, + Sessions = + [ + StandardInputDescriptor("one"), + StandardInputDescriptor("two"), + ], + }; + var operations = new FixtureSessionOperations(); + using var diagnostics = new StringWriter(); + using var input = new System.IO.StringReader( + "password-one" + Environment.NewLine + + "password-two" + Environment.NewLine); + using var host = new HeadlessProcessHost( + configuration, + HeadlessPathSet.Resolve(new HeadlessPathOverrides()), + input, + diagnostics, + operations, + new FakeTimeProvider(), + directCredentials: null, + consoleEnabled: true); + + Assert.Contains("single-session only", diagnostics.ToString()); + } + private static bool WaitForEndOfInput(HeadlessConsoleController controller) => controller.Reader.EndOfInput.Wait(TimeSpan.FromSeconds(5));