Skip to content

Restore terminal cursor visibility on exit in collect-linux#5707

Open
Copilot wants to merge 5 commits intomainfrom
copilot/fix-terminal-cursor-visibility
Open

Restore terminal cursor visibility on exit in collect-linux#5707
Copilot wants to merge 5 commits intomainfrom
copilot/fix-terminal-cursor-visibility

Conversation

Copy link
Contributor

Copilot AI commented Feb 7, 2026

dotnet-trace collect-linux hides the cursor during trace collection but never restores it, leaving users with an invisible cursor after the tool exits.

Changes

  • CollectLinuxCommand.cs: Add cursor restoration in finally block, matching the pattern in CollectCommand.cs

    • Only restores when !Console.IsOutputRedirected to avoid interfering with piped output
    • Guarantees restoration on all exit paths (normal, exception, cancellation)
  • CollectLinuxCommandFunctionalTests.cs: Add tests verifying cursor state after command exits

    • Success path
    • Error path
    • Redirected output path
finally
{
    if (!Console.IsOutputRedirected)
    {
        Console.CursorVisible = true;
    }
    // ... existing cleanup code
}

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • securitytools.pkgs.visualstudio.com
    • Triggering command: /opt/hostedtoolcache/CodeQL/2.24.0/x64/codeql/csharp/tools/linux64/Semmle.Autobuild.CSharp /opt/hostedtoolcache/CodeQL/2.24.0/x64/codeql/csharp/tools/linux64/Semmle.Autobuild.CSharp (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>dotnet-trace collect-linux leaves terminal cursor hidden after tracing completes</issue_title>
<issue_description>### Description

After dotnet-trace collect-linux finishes collecting a trace and exits, the terminal cursor remains hidden. The cursor is not restored to its visible state on exit, leaving the user with an invisible cursor in their terminal session.

Steps to Reproduce

  1. Open a terminal session.
  2. Run dotnet-trace collect-linux -p <pid> (or any valid collect-linux invocation).
  3. Wait for trace collection to complete, or stop it with Ctrl+C / Enter.
  4. Observe that the terminal cursor is no longer visible after the tool exits.
  5. The terminal is still functional (typing works), but the cursor is invisible.

Expected Behavior

After dotnet-trace collect-linux exits — whether through normal completion, user-initiated stop, or signal — the terminal cursor should be restored to its visible state. The tool should ensure terminal state cleanup happens on all exit paths.

Actual Behavior

The cursor remains hidden after the tool exits. Users must manually restore it by running tput cnorm or reset to get their cursor back.

Additional Context

  • The tool likely hides the cursor (e.g., via ANSI escape \e[?25l) during collection for TUI/progress rendering but does not emit the corresponding show-cursor sequence (\e[?25h) on exit.
  • The fix should ensure cursor restoration on all exit paths: normal exit, SIGINT, SIGTERM, and any error/panic paths.
  • Consider using a cleanup guard or defer/finally pattern to guarantee terminal state is restored regardless of how the tool exits.</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 4 commits February 7, 2026 08:45
- Add finally block to restore cursor visibility on all exit paths
- Check IsOutputRedirected before restoring cursor
- Add unit tests to verify cursor restoration behavior

Co-authored-by: hoyosjs <19413848+hoyosjs@users.noreply.github.com>
Remove manual cursor state manipulation in test to properly verify
that the finally block respects IsOutputRedirected flag

Co-authored-by: hoyosjs <19413848+hoyosjs@users.noreply.github.com>
Co-authored-by: hoyosjs <19413848+hoyosjs@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix hidden terminal cursor after dotnet-trace collect-linux Restore terminal cursor visibility on exit in collect-linux Feb 7, 2026
Copilot AI requested a review from hoyosjs February 7, 2026 08:51
@hoyosjs
Copy link
Member

hoyosjs commented Feb 7, 2026

I tested and this seems to be working correctly.

@hoyosjs hoyosjs marked this pull request as ready for review February 7, 2026 09:40
@hoyosjs hoyosjs requested a review from a team as a code owner February 7, 2026 09:40
Copilot AI review requested due to automatic review settings February 7, 2026 09:40
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes terminal state cleanup for dotnet-trace collect-linux by ensuring the cursor is restored after trace collection, and adds functional tests around cursor visibility behavior.

Changes:

  • Restore cursor visibility in CollectLinuxCommandHandler.CollectLinux via a finally block.
  • Add functional tests for cursor visibility on success, error, and redirected-output scenarios.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
src/Tools/dotnet-trace/CommandLine/Commands/CollectLinuxCommand.cs Adds cursor restoration logic in finally for collect-linux execution paths.
src/tests/dotnet-trace/CollectLinuxCommandFunctionalTests.cs Adds tests intended to validate cursor visibility restoration behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}
finally
{
if (!Console.IsOutputRedirected)
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cursor is always restored here even on paths where it was never hidden (e.g., --probe early return, or early returns before Console.CursorVisible = false), which can unexpectedly change the user's cursor state. Also, cursor visibility is still set to false unconditionally earlier, so with output redirection the cursor can be hidden but never restored. Consider only toggling cursor visibility when !Console.IsOutputRedirected, and track whether/what value you changed so the finally restores only when it was modified (ideally restoring the previous value).

Suggested change
if (!Console.IsOutputRedirected)
if (!Console.IsOutputRedirected && !Console.CursorVisible)

Copilot uses AI. Check for mistakes.
console.CursorVisible = true;

int exitCode = Run(TestArgs(), console);

Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exitCode is assigned but never used, which will produce a compiler warning that becomes an error in this repo (TreatWarningsAsErrors=true). Either assert on the exit code (e.g., Ok) or discard the value (e.g., _ = Run(...)).

Suggested change
// Command should succeed
Assert.Equal((int)ReturnCode.Ok, exitCode);

Copilot uses AI. Check for mistakes.
Comment on lines +263 to +264
int exitCode = Run(TestArgs(), console);

Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exitCode is assigned but never used, which will produce a compiler warning that becomes an error in this repo (TreatWarningsAsErrors=true). Either assert on the exit code or discard the value.

Copilot uses AI. Check for mistakes.
Comment on lines +263 to +267
int exitCode = Run(TestArgs(), console);

// When output is redirected, the cursor restoration code in the finally block
// should not execute, so the cursor should remain in the state set by line 104 (false)
Assert.False(console.CursorVisible, "Cursor should remain hidden when output is redirected");
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test asserts the cursor remains hidden when output is redirected, but the command currently also hides the cursor unconditionally earlier. If the intent is to avoid interfering with redirected/piped output, it would be better for the command to not change cursor visibility at all when IsOutputRedirected is true; then the test should assert the cursor remains in its original state. Also, the comment referencing a specific source line number is brittle and will go stale as the file changes.

Suggested change
int exitCode = Run(TestArgs(), console);
// When output is redirected, the cursor restoration code in the finally block
// should not execute, so the cursor should remain in the state set by line 104 (false)
Assert.False(console.CursorVisible, "Cursor should remain hidden when output is redirected");
bool initialCursorVisible = console.CursorVisible;
int exitCode = Run(TestArgs(), console);
// When output is redirected, the command should not change cursor visibility,
// so the cursor should remain in its original state.
Assert.Equal(initialCursorVisible, console.CursorVisible);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

dotnet-trace collect-linux leaves terminal cursor hidden after tracing completes

2 participants