Skip to content

Automatic scrolling of window when setting cursor position #14774

Open

Description

@ssbssa

Windows Terminal version

1.16.230126001

Windows build number

10.0.19044.2486

Other Software

No response

Steps to reproduce

I mentioned this first in #14759.

You can reproduce the problem with this test program:

#include <windows.h>

int main()
{
  HANDLE con = GetStdHandle(STD_OUTPUT_HANDLE);
  CONSOLE_SCREEN_BUFFER_INFO csbi;
  GetConsoleScreenBufferInfo(con, &csbi);

  // use scrollback of 100 lines
  COORD con_size = {
    csbi.srWindow.Right - csbi.srWindow.Left + 1,
    csbi.srWindow.Bottom - csbi.srWindow.Top + 1 + 100
  };
  SetConsoleScreenBufferSize(con, con_size);

  // write 50 lines so the position is not at the top of the scrollback
  for (int i = 0; i < 50; i++)
    WriteConsoleA(con, "line\n", 5, NULL, NULL);

  // stop at escape key
  HANDLE in = GetStdHandle(STD_INPUT_HANDLE);
  INPUT_RECORD ir;
  DWORD didread;
  while (ReadConsoleInput(in, &ir, 1, &didread))
  {
    if (ir.EventType == KEY_EVENT && ir.Event.KeyEvent.bKeyDown)
    {
      if (ir.Event.KeyEvent.wVirtualKeyCode == VK_ESCAPE)
        break;

      WriteConsoleA(con, &ir.Event.KeyEvent.uChar.AsciiChar, 1, NULL, NULL);
      GetConsoleScreenBufferInfo(con, &csbi);
      // re-set the cursor position where it already is, this is done
      // so the cursor is visible at the new location after the character
      // was printed
      SetConsoleCursorPosition(con, csbi.dwCursorPosition);
      // now the console window is scrolled so that the cursor is at the bottom
      // of the window.
    }
  }

  return 0;
}

There is a difference of behavior when tried with either the conhost.exe of the current Win10, or the OpenConsole.exe of Microsoft.WindowsTerminal_Win10_1.16.10261.0_8wekyb3d8bbwe.msixbundle.

Between entering some keys, scroll the window up or down with the mouse.

Expected Behavior

Once you enter a key, and the cursor position is re-set, it will scroll the window to the cursor, but only if the cursor was not already in the visible part of the window.
Which is exactly how it works in conhost.exe.

Actual Behavior

But with OpenConsole.exe 1.16.230126001, the window is always scrolled so that the cursor is at the bottom of the screen, even if it was already visible before.

Activity

  1. added
    Issue-BugIt either shouldn't be doing this or needs an investigation.
    Needs-TriageIt's a new issue that the core contributor team needs to triage at the next triage meeting
    on Feb 2, 2023
  2. added
    Product-ConhostFor issues in the Console codebase
    Area-OutputRelated to output processing (inserting text into buffer, retrieving buffer text, etc.)
    Area-ServerDown in the muck of API call servicing, interprocess communication, eventing, etc.
    and removed
    Needs-TriageIt's a new issue that the core contributor team needs to triage at the next triage meeting
    on Feb 2, 2023
  3. zadjii-msft commented on Feb 2, 2023

    @zadjii-msft
    Member

    Thanks for the repro!

  4. j4james commented on Feb 2, 2023

    @j4james
    Collaborator

    This change in behavior is almost certainly a result of PR #12972, and I suspect it could be difficult to resolve without reintroducing the virtual bottom bugs that PR was attempting to fix.

  5. rforzachamp821 commented on Jun 22, 2023

    @rforzachamp821

    Thanks for putting this issue down. I have the same issue and it really sucks, as there's no other API that can perform the functions with GetConsoleScreenBufferInfo() in the meantime.
    CONSOLE_SCREEN_BUFFER_INFO::dwCursorPosition.Y is also affected by this, and it maxes out at the vertical console window size when you hit the bottom of the console window to the point where the scrollbar shows up.

  6. 2 remaining items

  7. VoxCore84 commented on Apr 1, 2026

    @VoxCore84

    This bug is the root cause of scroll-jump issues in all terminal applications that use the Ink rendering framework (React-based terminal UI for Node.js), which calls SetConsoleCursorPosition on every render cycle.

    The most prominent affected application is Anthropic's Claude Code CLI, which has a large and growing Windows user base. Claude Code streams long-form output (code generation, file diffs, build results) while users need to scroll up to read prior context. Every render cycle triggers the viewport-snap-to-bottom described here, making it impossible to read scrollback during active output.

    The practical impact across the Claude Code community:

    • 6+ duplicate issues filed on the Claude Code repo (#33367 with 61 upvotes, #33814 with 30, Right Click Menu for Windows-Terminal in Chinese Environment #10835 with 15, #34242 with 9, plus #34400 and #34052)
    • Combined 100+ upvotes and 100+ comments across those issues
    • Multiple community-built workarounds (stdout interceptors, PTY proxies, tmux wrappers, snapOnOutput: false) — all attempting to work around this specific SetConsoleCursorPosition behavior
    • One user (dotiallwrong (@doitallwrong) on the Claude Code repo) decompiled the CLI, identified three specific functions emitting unclamped cursor-up sequences, and provided a sed patch — but the underlying Windows Terminal behavior described here is the aggravating factor that makes the cursor repositioning scroll the viewport even inside synchronized update blocks

    This affects any Ink-based application on Windows Terminal, not just Claude Code. Given that Ink is the standard framework for Node.js terminal UIs, the blast radius extends to the entire ecosystem of interactive CLI tools built with it.

  8. r800360 commented on Jul 14, 2026

    @r800360

    I would like to work on this.

    I traced the current behavior to ApiRoutines::SetConsoleCursorPositionImpl in src/host/getset.cpp. After #12972, the function determines the snap position relative to GetVirtualViewport(), moves the real viewport, and calls UpdateBottom(), even when the cursor is already visible in the real viewport.

    My proposed boundary is:

    1. If the rendered cursor cell is already inside GetViewport(), leave both the real viewport and virtual bottom unchanged.
    2. If the cursor is outside the real viewport, retain the existing virtual-viewport snapping and UpdateBottom() behavior.
    3. Add a regression case to ScreenBufferTests::UpdateVirtualBottomWithSetConsoleCursorPosition where the real and virtual viewports differ but the real viewport already contains the cursor.

    I would also manually validate the reproducer here, #1222, #9754, and cls behavior so this does not undo the cases protected by #12972.

    Mike Griese (@zadjii-msft), does that behavioral boundary look correct before I open a draft PR?

  9. j4james commented on Jul 15, 2026

    @j4james
    Collaborator
    1. If the rendered cursor cell is already inside GetViewport(), leave both the real viewport and virtual bottom unchanged.

    This doesn't sound right to me. The virtual bottom needs to be updated whenever the cursor is moved outside the virtual viewport boundaries. This can occur regardless of whether the cursor is inside the visible viewport or not.

    It's also worth noting that this issue is specific to ConHost/OpenConsole as far as I'm aware. So if you're trying to address an issue in Windows Terminal, like the complaint from the Claude Code user above, I suspect that's unrelated to this code.

  10. rforzachamp821 commented on Jul 15, 2026

    @rforzachamp821

    It's also worth noting that this issue is specific to ConHost/OpenConsole as far as I'm aware. So if you're trying to address an issue in Windows Terminal, like the complaint from the Claude Code user above, I suspect that's unrelated to this code.

    Yes. The real problem here is not that the Windows Terminal is doing it wrong, but it's that ConHost (conio.h) has been doing it wrong for so long. GetConsoleScreenBufferInfo() traditionally returns the position of the cursor relative to the entire scrollbuffer. The current implementation in OpenConsole will result in the returned CONSOLE_SCREEN_BUFFER_INFO object to contain the cursor co-ordinates for the visible buffer/current viewport area only. This is correct, but if we were to get Microsoft to fix Get/SetConsoleCursorPosition() to properly address this, it would break other API functions (specifically functions in conio.h) and legacy applications which still use the logic of the old method.

    Hence, I think this should be fixed as a profile-specific setting implementation. A simple toggle should do the trick. Enable the old method of cursor position logic (default, off), enable the new method (on). When off, the cursor position would be updated based on the position on the entire scrollbuffer. When on, the cursor position would be updated based on the viewport and restricted to that area, just like it is now.

    I believe this could work because an installation of Claude could just create a new profile on Windows Terminal sticking to the old method of console cursor position data. Then, the devs for those API tools can adapt their logic to the old method.

    All this is provided Microsoft isn't willing to fix their cursor functions AND conhost.

  11. DHowett commented on Jul 15, 2026

    @DHowett
    Member

    After #12972, it will return the position on the visible buffer/viewport only.

    I believe that to be a misapprehension of what that pull request does.

  12. rforzachamp821 commented on Jul 15, 2026

    @rforzachamp821

    After #12972, it will return the position on the visible buffer/viewport only.

    I believe that to be a misapprehension of what that pull request does.

    You are correct. It's been a while since i dug into this so I forgot all my PR numbers, LOL. Just axed it instead, edited.

  13. godot42x commented on Aug 16, 2026

    @godot42x

    Almost all AI agent CLIs have this issue: while working, the user is reading the output, and then the entire screen gets offset and scrolls to the top.

  14. DHowett commented on Aug 16, 2026

    @DHowett
    Member

    Almost all AI agent CLIs have this issue: while working, the user is reading the output, and then the entire screen gets offset and scrolls to the top.

    Agentic CLIs by and large use the alternate screen buffer and implement their own scrolling. There is nothing we can do about that.

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-OutputRelated to output processing (inserting text into buffer, retrieving buffer text, etc.)Area-ServerDown in the muck of API call servicing, interprocess communication, eventing, etc.Help WantedWe encourage anyone to jump in on these.Issue-BugIt either shouldn't be doing this or needs an investigation.Priority-1A description (P1)Product-ConhostFor issues in the Console codebase

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions