fix(headless): S7 report why a multi-session process never attaches the console

A configured process with 2+ sessions and --console silently skipped
console attachment (the plan's "single-session only for the first cut"),
indistinguishable from --console simply having worked. Report it
explicitly through the same HeadlessDiagnosticWriter.Message stream
every other structured event already uses.

TwoSessionsWithConsoleFlagReportsSingleSessionOnly was shown to fail
against the silent-skip branch (mutation: the else-branch body removed)
-- the diagnostics stream carried only the ordinary lifecycle/resource
JSON lines, with no "single-session only" message anywhere.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-09-07 08:07:41 +02:00
parent 2ff1e1228c
commit 314cb5f4a5
2 changed files with 60 additions and 0 deletions

View file

@ -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;
}

View file

@ -564,6 +564,58 @@ public sealed class HeadlessConsoleTests
Assert.Equal(expectDimmed, text.Contains("[2m"));
}
/// <summary>
/// S7: a multi-session process with <c>--console</c> 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 <c>HeadlessDiagnosticWriter.Message</c>'s "console"
/// category.
/// </summary>
[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));