Skip to content

Closing docked container terminals leaves shell processes running #20440

Description

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Clicking X on an AppHost-owned terminal's dock tab removes the tab, but when its workload is docker exec -it <container> /bin/sh, the shell inside the container can remain running. Repeatedly opening and closing docked shells accumulates processes with no corresponding terminal tabs.

This is misleading: closing a terminal looks like it ends that shell session. Users lose the visible session while its process remains in the container. For workloads other than an idle shell, this could also mean work continues after users believe they have stopped it.

The reported resource had both a resource-owned main shell (.WithTerminal()) and AppHost-owned docked exec shells. After closing earlier tabs and leaving one docked shell open, the expected total was two shell processes: the container's main shell and the remaining exec shell. Instead, nine shell processes remained.

The remaining docked terminal in the screenshot is detached into a separate browser window and should still be alive. This report concerns closing other sessions with the dock tab's X, not merely closing a detached browser window or navigating away from a terminal view.

One resource-owned terminal and one remaining docked terminal, but numerous shell processes are still running

Expected Behavior

Clicking X must terminate the process running in that docked terminal, including the shell inside the container, not merely dispose the local docker exec client or remove the tab. It must leave the resource's main process and unrelated terminal sessions running.

With one resource-owned main shell and one remaining docked shell, there should be only two shell processes. Repeatedly opening and closing docked shells must not accumulate leftover processes. Users should not have to type exit before clicking X for that session's process to be terminated.

Steps To Reproduce

Use an AppHost build containing the docked terminal support from #19887 and Docker:

#pragma warning disable ASPIRETERMINAL001

using Aspire.Hosting.ApplicationModel;
using Microsoft.Extensions.DependencyInjection;

var builder = DistributedApplication.CreateBuilder(args);
const string containerName = "aspire-terminal-close-repro";

var demo = builder.AddContainer("terminal-demo", "alpine")
    .WithContainerName(containerName)
    .WithArgs("/bin/sh", "-i")
    .WithTerminal();

demo.WithCommand("open-shell", "Open shell", context =>
{
    var terminals = context.Services.GetRequiredService<TerminalService>();
    var terminal = terminals.CreateTerminal(new TerminalLaunchOptions
    {
        Title = "Container shell",
        Executable = "docker",
        Arguments = ["exec", "-it", containerName, "/bin/sh"],
        Placement = TerminalPlacement.Dock
    });
    terminal.Start();
    terminal.Show();
    return Task.FromResult(CommandResults.Success());
});

builder.Build().Run();
  1. Start the AppHost and wait for terminal-demo to be running.
  2. Open its Console Logs > Terminal view to interact with the resource-owned main shell.
  3. Execute Open shell several times to create separate docked shells.
  4. Close all but one of those sessions using the dock tabs' X buttons, without typing exit first.
  5. In the resource-owned terminal, run ps -o pid,ppid,stat,args (the screenshot uses BusyBox ps faux).
  6. Observe that the closed sessions' /bin/sh processes remain.

The coexistence of resource-owned and AppHost-owned terminals makes this easy to observe; it has not been established as a necessary condition for the leak.

Exceptions (if any)

None reported. Tabs close, but the remote shells remain.

Aspire doctor output

Not collected. Environment: Windows, .NET SDK 10.0.302, Aspire.AppHost.Sdk and hosting packages 13.6.0-preview.1.26473.12, Docker, and alpine:latest reporting Alpine 3.24.2. This was observed while testing the preview terminal feature; a stable-version comparison was not performed.

Anything else?

Read-only inspection of the reported running environment confirmed:

  • One main /bin/sh -i process (PID 1).
  • Eight additional /bin/sh processes, all in a sleeping rather than zombie state.
  • Only one live local docker.exe exec -it ... /bin/sh client targeting this container remained.

Together with the user's close-button sequence, this is consistent with seven remote shells left behind after their local terminal clients were closed. Inspection did not stop the container or kill any processes; an independent automated browser replay was not performed.

Likely cause and existing acknowledgement: the AppHost owns the local docker exec client, while Docker manages the shell inside the container. Terminating the client does not guarantee termination of that remote process. This is tracked upstream in moby/moby#9098.

Aspire's close path does request terminal disposal (TerminalDock.CloseTerminalAsync -> DashboardService.CloseTerminal -> terminal disposal). Hex1bAspireTerminal.StopCoreAsync cancels and joins local PTY teardown. This report is therefore specifically about remote container-process cleanup, not a claim that the close action never disposes the local terminal.

The same caveat is already acknowledged in src/Shared/ContainerReplCommand.cs and the documentation added by #20231, which advises quitting database clients before closing their tabs. That is useful documentation but does not fix the processes remaining alive after their tabs are closed.

For an idle shell, typing exit before closing its tab is the current workaround. This should not require stopping the entire container, since doing so also terminates its main resource process and every other exec session.
Possible interim UX mitigation, not the requested fix: if reliable remote termination cannot be delivered immediately, a warning before closing could explain that the process may remain alive and let the user return to the terminal to exit cleanly. This is only an alternative to consider, not a required feature or a resolution of this bug. Automatically typing exit is not a general cleanup mechanism because the foreground application may not be a shell.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-dashboardarea-terminalTerminal/PTY support — TerminalHost, WithTerminal, dashboard terminal viewtriage:bot-seenAspire triage bot has seen this issue

    Type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions